<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Eevis Blog</title>
  <subtitle>Posts about accessibility, being a (woman) developer and life in general.</subtitle>
  <link href="https://eevis.codes/feed.xml" rel="self" />
  <link href="https://eevis.codes/blog" />
  <updated>2026-03-07T13:05:16.906Z</updated>
  <id>https://eevis.codes/blog</id>
  <author>
    <name>Eevis Panula</name>
    <email>hello@eevis.codes</email>
  </author> 
  
  <entry>
    <title>Why Playing Team Sports Makes Me a Better Developer</title>
    <link href="https://eevis.codes/blog/2021-05-12/why-playing-team-sports-makes-me-a-better-developer/" />
    <updated>2023-01-03T08:57:51.135Z</updated>
    <id>https://eevis.codes/blog/2021-05-12/why-playing-team-sports-makes-me-a-better-developer/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1GPr43cyVEcjlxXIN71vWb/8ae5869bb06b28bc4ec1271420c36811/isaiah-rustad-8q5rl4rdK_0-unsplash.jpg"/>]]>
      &lt;p&gt;I&amp;#39;ve played team sports since I was something like ten. First, it was &lt;a href=&quot;https://en.wikipedia.org/wiki/Floorball&quot;&gt;floorball&lt;/a&gt; - this fantastic indoor sport, where you have a stick, a ball, and goals. Something similar to hockey, but less violent. And after all, completely different. &lt;/p&gt;
&lt;p&gt;Then I moved to another city and soon realized that it&amp;#39;s not as fun as it used to be with the team I had played with for 10 years. I switched to &lt;a href=&quot;https://en.wikipedia.org/wiki/Ultimate_(sport)&quot;&gt;ultimate frisbee&lt;/a&gt;, which is something I still play. I also did a tour in the roller derby world, loved the sport but got a brain injury. I realized that if I want to be able to do my job, I need to let this awesome thing go. I&amp;#39;m sometimes still sad about it.&lt;/p&gt;
&lt;p&gt;So, I have a history (and hopefully future) with team sports. I&amp;#39;ve done some coaching during the years and participated in different boards - be it a club or national level organization. So I could say I&amp;#39;ve gotten a lot from this and also learned a ton. &lt;/p&gt;
&lt;p&gt;One day, while I was doing some ladder drills (they&amp;#39;re for agility on the field), I started thinking if there has been anything I&amp;#39;ve used from my sports background in my day-to-day job. After pondering for a while, I realized that yes, there is. In this blog post, I will share some of those thoughts.&lt;/p&gt;
&lt;p&gt;Many other activities may provide just the same benefits. However, my background is in team sports, so that&amp;#39;s why my angle is from the perspective of team sports.&lt;/p&gt;
&lt;h2 id=&quot;team-spirit-of-the-game&quot;&gt;Team Spirit (of the Game)&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;re not familiar with ultimate frisbee, you might wonder what the heck the title of this section means. Let me explain. In ultimate frisbee, there is this thing called &lt;a href=&quot;https://en.wikipedia.org/wiki/Ultimate_(sport)#Spirit_of_the_game&quot;&gt;&amp;quot;Spirit of the Game,&amp;quot;&lt;/a&gt; which has to do with, for example, fairness of the game and sportsmanship. It&amp;#39;s something that makes the sport possible to play without any referees (yup, you read it right!). &lt;/p&gt;
&lt;p&gt;During the years, being part of a team has indeed taught me to be a better team member.  Especially being part of teams that comply with such philosophy as the Spirit of the Game. I know how to work in a group, and also that different people have distinct strengths. Also, I&amp;#39;ve learned to get along with a very diverse set of characters - not all of them &amp;quot;click&amp;quot; with me, but still, we are a team, and we need to work together. &lt;/p&gt;
&lt;p&gt;Team sports are never about one individual. There are 5-7-ish people on the field (depending on the sport), and everyone is needed to score. This makes me try to do my best and never let the team down. &lt;/p&gt;
&lt;h2 id=&quot;perseverance&quot;&gt;Perseverance&lt;/h2&gt;
&lt;p&gt;As the season approaches, there is usually some kind of goal. Some years, for my team, it has been the national championships. That goal has been the guide for every practice and game. It&amp;#39;s a long time from the pre-season to the final match, and perseverance is needed. &lt;/p&gt;
&lt;p&gt;The same has been true with every development project I&amp;#39;ve been in. Development projects tend to be long, and there are always moments when I want to give up. Learning perseverance from sports helps me to put those moments in context. It also gets me through that valley of whatever it is that makes me feel like giving up. &lt;/p&gt;
&lt;h2 id=&quot;willingness-to-do-voluntary-work&quot;&gt;Willingness to Do Voluntary Work&lt;/h2&gt;
&lt;p&gt;I grew up in an environment where doing voluntary work for the club was expected. So I&amp;#39;ve spent countless days raising money, officiating games, cleaning up places, and so forth. Whenever there is a new &amp;quot;working together day&amp;quot; with my club, my first thoughts are, &amp;quot;ok, this is mandatory.&amp;quot; (I&amp;#39;m not sure of the correct translation of the word; for those who understand Finnish, I mean &amp;quot;talkoot.&amp;quot;)&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve come to realize that this is not the case for everyone. Of course, not everyone can give their time, because sometimes life gets in the way, but I&amp;#39;m talking more about attitude here.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve been reflecting on the topic a lot during the past couple of weeks. On Friday 23rd of April, I received the first-ever Mimmit Koodaa-award for my work for the &lt;a href=&quot;https://mimmitkoodaa.ohjelmistoebusiness.fi/in-english/&quot;&gt;Mimmit Koodaa-community&lt;/a&gt;. This community (translates roughly &amp;quot;Women code&amp;quot;) is helping to bring more women into tech.&lt;/p&gt;
&lt;p&gt;The justification for me getting the award was all the hard work I&amp;#39;ve done for the community. I&amp;#39;ve gotten so many praises, and &amp;quot;yeah, it was definitely the right person who got the award&amp;quot;s. However, every time I hear, &amp;quot;you&amp;#39;ve worked so hard!&amp;quot; I instantly think, &amp;quot;Umm... but isn&amp;#39;t that something that every single one would do?&amp;quot;. Apparently not.   &lt;/p&gt;
&lt;p&gt;So that&amp;#39;s the attitude and position where I am because of my background. This is not to brag or anything. I&amp;#39;m just trying to reflect on some thoughts and how my experience affects the work I do - at my day job and outside of it.  &lt;/p&gt;
&lt;h2 id=&quot;getting-away-from-work-thoughts&quot;&gt;Getting Away from Work Thoughts&lt;/h2&gt;
&lt;p&gt;While the previous ones are something that I&amp;#39;m going to carry with me for the rest of my life, the next ones are about the direct consequences of doing sports.&lt;/p&gt;
&lt;p&gt;After a long, intensive day at work, it is sometimes hard to let go of those work-related thoughts. They just keep going around and around and make me anxious because I know I shouldn&amp;#39;t be thinking about them and because I can&amp;#39;t do anything to them at that point. Going into practice, where I need to give 100% of my concentration to the drills and game, definitely helps me let go of those thoughts.&lt;/p&gt;
&lt;p&gt;You could argue that any sporting activity would do. Well, for me, going for a run alone would mean that the thoughts are still there, and I would go even deeper into them because I&amp;#39;d be alone with those thoughts. However, being around people who have nothing to do with my work and doing something completely different helps me forget. &lt;/p&gt;
&lt;h2 id=&quot;keeping-my-brain-functional&quot;&gt;Keeping My Brain Functional&lt;/h2&gt;
&lt;p&gt;For developers, our brain is the most crucial tool for doing our jobs. I&amp;#39;ve faced the situation where I needed to give something up to keep the risk of losing anything more from the brain capacity I have. So I could say I&amp;#39;ve given some thought to this.&lt;/p&gt;
&lt;p&gt;This point, however, is not just about protecting our heads from injuries. I&amp;#39;m talking about the effects actively moving has on our brain. As &lt;a href=&quot;https://www.health.harvard.edu/blog/regular-exercise-changes-brain-improve-memory-thinking-skills-201404097110&quot;&gt;Heidi Godman writes&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Exercise changes the brain in ways that protect memory and thinking skills.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To be precise, the effects were visible from regular aerobic exercises. For example, muscle training didn&amp;#39;t have the same results in the study. If you want to learn more, I highly recommend checking the article linked. &lt;/p&gt;
&lt;p&gt;So, being part of a sports team also requires me to continuously be active, whether in-season or off-season. Luckily this is good for my brain health too. And, as said in the previous point, another part is that I have some mechanisms to let go of those work-related thoughts, so that&amp;#39;s really good for my brain too.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So, I get numerous benefits from team sports - both from my long history with them and from actively being part of a team. Some of them are skills that can be directly used in the teams and projects at work, and some of them help me to be healthy enough to do my job. &lt;/p&gt;
&lt;p&gt;Coming back to the title of this blog post, you might wonder how this makes me a better developer while I haven&amp;#39;t really touched coding at all. Well, the most important aspect of the development is not coding. I think being a good developer is more about communication and team skills. &lt;/p&gt;
&lt;p&gt;Recovering after the day at work is essential. I&amp;#39;ve worked with many people on the edge of burnout. They&amp;#39;re usually not the nicest members of the team to work with because of the stress and other symptoms they have.  Heck, I&amp;#39;ve been one too, and I wouldn&amp;#39;t want to work with me from those times. So taking care of yourself is not only for you, it is for your team too. And I believe that makes a better developer.&lt;/p&gt;
&lt;p&gt;Do you play any sports? Or have you noticed benefits or learnings you get from leisure activities that are usable in your day-to-day job?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@isaiahrustad?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Isaiah Rustad&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Let&#39;s Talk About Accessibility</title>
    <link href="https://eevis.codes/blog/2021-05-21/lets-talk-about-accessibility/" />
    <updated>2023-01-03T08:57:50.254Z</updated>
    <id>https://eevis.codes/blog/2021-05-21/lets-talk-about-accessibility/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2PYSzzdBokMHTf4ywmO3D0/d2a4ac280c57ac252058b8c48d15a457/james-sutton-AcL5SitD8Wg-unsplash.jpg"/>]]>
      &lt;p&gt;Yesterday (20th of May 2021) marked the 10th &lt;a href=&quot;https://globalaccessibilityawarenessday.org/about/&quot;&gt;Global Accessibility Awareness Day&lt;/a&gt;, celebrated on the third Thursday of each May. It started from &lt;a href=&quot;https://mysqltalk.wordpress.com/2011/11/27/challenge-accessibility-know-how-needs-to-go-mainstream-with-developers-now/&quot;&gt;a single blog post from Joe Devon&lt;/a&gt; and has grown into a global event since then. &lt;/p&gt;
&lt;p&gt;One example of what Global Accessibility Awareness Day, or GAAD, has achieved is &lt;a href=&quot;https://diamond.la/gaadpledge/&quot;&gt;GAAD Pledge&lt;/a&gt;. The first company to take the pledge was Facebook, committing to make the React Native framework fully accessible.&lt;/p&gt;
&lt;h2 id=&quot;why-is-gaad-important&quot;&gt;Why is GAAD Important?&lt;/h2&gt;
&lt;p&gt;I believe raising awareness about accessibility is essential. I mean, if we, who are creating the web, aren&amp;#39;t aware of these things, how in the heck can we assume the sites and apps will be built in a way that everyone, whether they have a disability or not, can use them?&lt;/p&gt;
&lt;p&gt;But it is not just that. The importance of raising awareness is not that only developers would know; it&amp;#39;s about the other roles too. If designers ignore accessibility, some things (say, color choices) are hard to fix on the developers&amp;#39; end. If the product owner cares more about IE6 users than accessibility, well, those bugs get prioritized. If the people who decide on the budget see other things as more important, developers can&amp;#39;t fix that. &lt;/p&gt;
&lt;p&gt;WebAIM has conducted its &lt;a href=&quot;https://webaim.org/projects/million/&quot;&gt;yearly analysis&lt;/a&gt; on the top million sites on the internet. In February 2021, 97.4% of these sites had detectable accessibility errors. And as they&amp;#39;ve used an automated tool for the analysis, it doesn&amp;#39;t even catch all the failures. (Automated testing tools catch only about &lt;a href=&quot;https://accessibility.blog.gov.uk/2017/02/24/what-we-found-when-we-tested-tools-on-the-worlds-least-accessible-webpage/&quot;&gt;15-40% of the accessibility failures.&lt;/a&gt;) So this means that sites like Google, Twitter, Youtube, Facebook, and others on the top million sites list have many accessibility barriers. &lt;/p&gt;
&lt;p&gt;About 1 billion people worldwide need websites to be designed and developed with accessibility in mind in order to be able to use those sites. If we don&amp;#39;t do that, we are excluding a hell of a number of people. You know, actual people, and not just numbers. &lt;/p&gt;
&lt;h2 id=&quot;but-its-not-only-about-the-websites&quot;&gt;But it&amp;#39;s Not Only About the Websites&lt;/h2&gt;
&lt;p&gt;Okay, I&amp;#39;ve been writing from a perspective of a (web) developer and touched on a couple of other roles that are part of creating sites and apps. How about the others? Don&amp;#39;t they need to know about accessibility? Well, yes, they do. &lt;/p&gt;
&lt;h3 id=&quot;its-about-social-media&quot;&gt;It&amp;#39;s About Social Media&lt;/h3&gt;
&lt;p&gt; Let&amp;#39;s think about, for example, social media for a while. Even if the app or site is developed to be accessible, the content creators may make it inaccessible. Adding alternative texts or image captions is the content creators&amp;#39; responsibility, as are adding captions, transcripts, and audio descriptions for videos and audios. &lt;/p&gt;
&lt;p&gt;If you are now wondering what the heck am I writing here, let me explain— a person who can&amp;#39;t see the picture benefits from a descriptive image caption. You know, all the information they would get from that image if they saw it. &lt;/p&gt;
&lt;p&gt;In the same way, if a person can&amp;#39;t hear, due to being deaf or, as in my case, just browsing the Instagram stories without sound, having captions for those videos is crucial. Audio descriptions of a video are essential for those who can&amp;#39;t see. And for those, who are, for example, deafblind, transcripts are a must. &lt;/p&gt;
&lt;p&gt;And oh, why I&amp;#39;m using phrases like &amp;quot;those who can&amp;#39;t see/hear&amp;quot; instead of, for example, a person who is blind or deaf, is because this goes further than that. As with all the accessibility improvements, we all could benefit. As mentioned, I rarely watch Instagram stories with a volume on, so when people add captions, that means I get the gist of the story. And then I might watch it, not just skip it.&lt;/p&gt;
&lt;h3 id=&quot;its-about-the-language&quot;&gt;It&amp;#39;s About the Language&lt;/h3&gt;
&lt;p&gt;Another thing is the language we use. This topic is two-sided: First, I want to talk about plain language, and then a bit about ableism. &lt;/p&gt;
&lt;h4 id=&quot;plain-language&quot;&gt;Plain Language&lt;/h4&gt;
&lt;p&gt;International Plain Language Federation &lt;a href=&quot;https://www.iplfederation.org/plain-language/&quot;&gt;defines the plain language&lt;/a&gt; with the following words:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A communication is in plain language if its wording, structure, and design are so clear that the intended readers can easily find what they need, understand what they find, and use that information.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, this basically means avoiding jargon and getting to the point as fast as possible. If you&amp;#39;re interested in learning more about writing clearly and simply, &lt;a href=&quot;https://webaim.org/techniques/writing/&quot;&gt;WebAIM has got you covered.&lt;/a&gt;. &lt;/p&gt;
&lt;h4 id=&quot;ableism&quot;&gt;Ableism&lt;/h4&gt;
&lt;p&gt;Another point about the language I wanted to raise is ableism, especially in the language we use. There are certain phrases that are part of the language that we use without thinking.  Like, have you ever said that &amp;quot;This week has been crazy!&amp;quot; or &amp;quot;I&amp;#39;m feeling autistic right now&amp;quot; or similar? Well, you know, that&amp;#39;s not okay. &lt;/p&gt;
&lt;p&gt;You might feel offended right now because you feel like I&amp;#39;m blaming you for something, but that&amp;#39;s not the case. We all use language unconsciously, and if we&amp;#39;ve learned the phrases, they come naturally. So I&amp;#39;m not assuming that you&amp;#39;re doing this on purpose, just out of not knowing. And that is fine because now you know and can do something about it. Changing the language we use is a long process (heck, I still make mistakes almost every day), but it is worth it.&lt;/p&gt;
&lt;p&gt;Here is an excellent article from &lt;a href=&quot;https://hbr.org/2020/12/why-you-need-to-stop-using-these-words-and-phrases&quot;&gt;Rakshitha Arni Ravishankar&lt;/a&gt; explaining more on the reasons why such language is harmful and what we can do about that. &lt;/p&gt;
&lt;h2 id=&quot;so-what-can-i-do&quot;&gt;So What Can I Do?&lt;/h2&gt;
&lt;p&gt;Okay, I got you there, right? And now you know that something needs to be done. It might feel like a lot and overwhelming, but let&amp;#39;s take one step at a time. I could give a long list of things to do, but let&amp;#39;s start with two.&lt;/p&gt;
&lt;h3 id=&quot;educate-yourself&quot;&gt;Educate Yourself&lt;/h3&gt;
&lt;p&gt;First, educate yourself. If you&amp;#39;re someone in charge of creating sites or apps, learn how to make them accessible. Learn about people with disabilities and their needs. Listen to their stories. &lt;/p&gt;
&lt;p&gt;I&amp;#39;m sharing some of my favorite resources here, both from the technical and more general perspective: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bemyeyes.com/podcasts-show/13-letters&quot;&gt;13 Letters Podcast&lt;/a&gt; - A podcast about accessibility with super interesting topics and guests&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.a11yproject.com/resources/&quot;&gt;The A11y Project Resources&lt;/a&gt; - A huge collection of resources about accessibility. &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://a11ytalks.com/&quot;&gt;A11y Talks&lt;/a&gt; - Virtual meetup about accessibility. Topics covered have been broad, from marketing to development.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webaim.org/articles/&quot;&gt;WebAIM Articles&lt;/a&gt; - A collection of resources about disabilities, digital accessibility, laws, and other relevant things&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;check-your-content-and-language&quot;&gt;Check Your Content and Language&lt;/h3&gt;
&lt;p&gt;So, another thing that you could do is to check the content you create. It&amp;#39;s possible to add alternative text to images. Here are links to the instructions in some significant social media sites: &lt;a href=&quot;https://www.facebook.com/help/214124458607871&quot;&gt;Facebook&lt;/a&gt;, &lt;a href=&quot;https://www.facebook.com/help/instagram/503708446705527&quot;&gt;Instagram&lt;/a&gt;, &lt;a href=&quot;https://www.linkedin.com/help/linkedin/answer/109799/adding-alternative-text-to-images-for-accessibility?lang=en&quot;&gt;LinkedIn&lt;/a&gt;, &lt;a href=&quot;https://help.twitter.com/en/using-twitter/picture-descriptions&quot;&gt;Twitter&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;If you have videos or audio-only content (podcasts are a good example), make sure that everyone can use that content. Add captions, transcripts, and audio descriptions, whichever are needed for that particular content. &lt;/p&gt;
&lt;p&gt;Also, be conscious of your language. If you find yourself using ableist phrases, start making an effort to change those phrases. There are always alternatives.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;I hope that this blog post has raised awareness about accessibility for you and that you&amp;#39;ve learned something new. It would be awesome to hear if that has happened, so please share your learnings! &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href=&quot;https://unsplash.com/@jamessutton_photography?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;James Sutton&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Don&#39;t Develop Just for Yourself - A Developer&#39;s Checklist to Accessibility</title>
    <link href="https://eevis.codes/blog/2021-05-28/dont-develop-just-for-yourself-a-developers-checklist-to-accessibility/" />
    <updated>2023-01-03T08:57:24.476Z</updated>
    <id>https://eevis.codes/blog/2021-05-28/dont-develop-just-for-yourself-a-developers-checklist-to-accessibility/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/66N9L5tLTVFLyCrBwPiWeo/68b88e9825a83d1653b9789d5a1ab343/glenn-carstens-peters-RLw-UC03Gwc-unsplash.jpg"/>]]>
      &lt;p&gt;We, as developers, tend to develop sites unconsciously for people like ourselves. If we don&amp;#39;t actively pay attention, the sites are often accessible only for certain types of people:  Sighted mouse-users, who have good fine motor skills and are good at using computers. &lt;/p&gt;
&lt;p&gt;It leads to moments where no one who navigates the web with only a keyboard (or keyboard simulating device) can access and interact with the site. It is a pretty big group of people!&lt;/p&gt;
&lt;p&gt;We have a responsibility to make sites that work for all of our users. In some cases, it is required by law, but it is not the only reason we should care. &lt;/p&gt;
&lt;p&gt;This blog post is aimed for those at the beginning of their accessibility journey. You might know some things, like &amp;quot;always provide an alt-text for an image,&amp;quot; but are unsure what to write there. Or you don&amp;#39;t have any idea how to know if your site is accessible at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Following this blog post won&amp;#39;t make your site 100% accessible!&lt;/strong&gt; The goal of this post is to provide some information about some things to check manually after using some automated testing. &lt;/p&gt;
&lt;h2 id=&quot;keyboard-navigation&quot;&gt;Keyboard Navigation&lt;/h2&gt;
&lt;p&gt;First of all, abandon your mouse for a second. Don&amp;#39;t use it. Try to navigate through the website using only the keyboard. Try to complete every task user needs to be able to complete on the website. Can you do it? Can you see where you are at the moment? Is the focus indicator (yes, that thing many designers and developers like to remove) visible enough? &lt;/p&gt;
&lt;p&gt;If you can&amp;#39;t use the whole website with only a keyboard, that is an accessibility and usability problem. However, one thing to note here is that keyboard interaction is not necessarily accomplished with just &lt;kbd&gt;tab&lt;/kbd&gt; and &lt;kbd&gt;enter&lt;/kbd&gt;, as it has been at some point. The general pattern goes, as &lt;a href=&quot;https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_generalnav&quot;&gt;WAI-ARIA Authoring Practices&lt;/a&gt; state:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A primary keyboard navigation convention common across all platforms is that the &lt;kbd&gt;tab&lt;/kbd&gt; and &lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;tab&lt;/kbd&gt; keys move focus from one UI component to another while other keys, primarily the arrow keys, move focus inside of components that include multiple focusable elements.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Another thing to consider in this point is focus indicators. They should be visible, and even if the default focus indicator on the browser is sufficient to pass the WCAG-requirements, it might be hard to see. So I would recommend enhancing the focus indicator to be more visible. Just remember that the color of the indicator should have a sufficient contrast ratio with the adjacent background.&lt;/p&gt;
&lt;h3 id=&quot;action-item-for-keyboard-navigation&quot;&gt;Action Item for Keyboard Navigation&lt;/h3&gt;
&lt;p&gt;Using only your keyboard, navigate through your site. Pay attention to navigation menus and other custom widgets that might lose focus because some elements are hidden incorrectly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is everything reachable and operable without a mouse? Can you see the focus every time?&lt;/strong&gt; &lt;/p&gt;
&lt;h2 id=&quot;alt-texts&quot;&gt;Alt-Texts&lt;/h2&gt;
&lt;p&gt;Another thing to check manually is alternative texts for the pictures. You might wonder, isn&amp;#39;t it a thing automated testing catches? Well, yes. Automated testing tools crawl through the markup, and when they find an &lt;code&gt;img&lt;/code&gt;-element, they check if the &lt;code&gt;alt&lt;/code&gt;-attribute is present. They don&amp;#39;t (and can&amp;#39;t) check the quality of the alternative texts.&lt;/p&gt;
&lt;p&gt;Not all images need alternative texts; however, they do need the &lt;code&gt;alt&lt;/code&gt;-attribute. So for a purely decorative image, you need to provide an empty &lt;code&gt;alt&lt;/code&gt;-attribute. This &amp;quot;hides&amp;quot; the image from the screen reader so that it won&amp;#39;t get read at all. &lt;/p&gt;
&lt;p&gt;You might ask, shouldn&amp;#39;t screen reader users know about everything on the page? A person, who consumes the web by listening, might want to reduce the amount of information. &lt;/p&gt;
&lt;h3 id=&quot;action-item-for-alt-texts&quot;&gt;Action Item for Alt-Texts&lt;/h3&gt;
&lt;p&gt;Go through every image on the page. You can use, for example, &lt;a href=&quot;https://chrispederick.com/work/web-developer/&quot;&gt;Web Developer-extension&lt;/a&gt; or the developer tools and check the image&amp;#39;s alt attribute. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is the text descriptive? Or is the picture decorative?&lt;/strong&gt; You can find more information on how you should write the alt-text for every type of image from &lt;a href=&quot;https://webaim.org/techniques/alttext/&quot;&gt;WebAIM: Alternative Text&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;page-language&quot;&gt;Page Language&lt;/h2&gt;
&lt;p&gt;Another thing that automated tests catch, if it&amp;#39;s missing, is the page language. Some of the project starters, such as &lt;code&gt;create-react-app,&lt;/code&gt; have the lang-attribute set automatically, but some (I&amp;#39;m looking at you, NextJS) don&amp;#39;t.  &lt;/p&gt;
&lt;p&gt;If the site&amp;#39;s language is English, you&amp;#39;re pretty much okay with the automatic language (if it exists). However, if it is not, then you need to change it. The reason is that the language screen readers use for the page comes from that &lt;code&gt;lang&lt;/code&gt;-attribute. So if you have a page with content in Finnish, and there is that &lt;code&gt;en&lt;/code&gt;-attribute, a screen reader would pronounce every word with an English accent. That&amp;#39;s not beautiful. Here&amp;#39;s an example with another way around. It&amp;#39;s almost as bad:&lt;/p&gt;
&lt;video controls=&quot;&quot;&gt;
      &lt;source src=&quot;https://videos.ctfassets.net/sk24pdnnlwhc/4cOy4EUnFHq4F1RMqL588x/22feb097de58d5c23c27aea967654fc8/page-language.mp4&quot; type=&quot;video/mp4&quot; /&gt;
   &lt;/video&gt;

&lt;h3 id=&quot;action-item-for-page-language&quot;&gt;Action Item for Page Language&lt;/h3&gt;
&lt;p&gt;Check the language of the page in the page&amp;#39;s &lt;code&gt;html&lt;/code&gt;-attribute. You can do this from developer tools&amp;#39; Elements-tab. &lt;strong&gt;Is the language code the same as the primary language on the page?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;text-resizing&quot;&gt;Text Resizing&lt;/h2&gt;
&lt;p&gt;Some people increase the text size of the webpage with, for example, browser settings. It means that the content takes more space and might flow differently. Developers should test if the site is working when text size has been increased to at least 200%. &lt;/p&gt;
&lt;h3 id=&quot;action-item-for-text-resizing&quot;&gt;Action Item for Text Resizing&lt;/h3&gt;
&lt;p&gt;Have the webpage on two browsers open, preferably side by side. In the first browser, use browser&amp;#39;s built-in zooming for testing by using &lt;kbd&gt;CMD&lt;/kbd&gt;+&lt;kbd&gt;+&lt;/kbd&gt; if you&amp;#39;re using a Mac, and &lt;kbd&gt;CTRL&lt;/kbd&gt;+&lt;kbd&gt;+&lt;/kbd&gt; if you&amp;#39;re using Windows or Linux. Increase the size to the maximum, which should be 200%. Compare the zoomed site and the one without a zoom.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does your site still work? Is the text flowing okay? Does increasing the text size add horizontal scrolling? Is all the information still there?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;color-alone&quot;&gt;Color Alone&lt;/h2&gt;
&lt;p&gt;If color alone conveys meaning, there are lots of users who miss out on that information. For example, suppose there is a list of different activities, and their level of advancement is communicated only with a colored box. In that case, there are some groups of people who can&amp;#39;t access that information. &lt;/p&gt;
&lt;p&gt;For example, a person who is color blind can&amp;#39;t separate certain color combinations, and thus the information, when it&amp;#39;s conveyed only with color, is not understandable. Also, for screen reader users, the color does not say anything; screen readers don&amp;#39;t say anything about the web page&amp;#39;s styles. &lt;/p&gt;
&lt;h3 id=&quot;action-item-for-color-alone&quot;&gt;Action Item for Color Alone&lt;/h3&gt;
&lt;p&gt;One thing that could be used as help for testing if the color alone is used to convey a meaning is to change the site into grayscale.  &lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;How to turn a page grayscale?&lt;/summary&gt;
You can turn a page grayscale from the developer tools in Chrome and Edge from the &quot;Rendering&quot;-tab. You can find it with the same instructions as for the [`prefers-reduced-motion`-simulation][3]. There is a header &quot;Emulate vision deficiencies&quot; on that tab, and you can choose Achromatopsia from the select.
&lt;/details&gt;

&lt;p&gt;&lt;strong&gt;Can you still understand all the information on the page?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;mobile-zooming&quot;&gt;Mobile Zooming&lt;/h2&gt;
&lt;p&gt;I remember, that at some point it was customary to add this &lt;code&gt;&amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&amp;quot; /&amp;gt;&lt;/code&gt; to disable the pinch-zoom on webpages. Nobody ever explained why it was, but it was everywhere I copied code to my projects. I didn&amp;#39;t know then that it would make zooming on touch devices harder. &lt;/p&gt;
&lt;p&gt;Some people need to use zoom to see the elements on the page. Also, many people want to, for example, zoom in on pictures to see how they look in that picture or some other detail. And sometimes, the website is unusable without zooming. That is a thing that would need fixing from the developers&amp;#39; side, but things don&amp;#39;t always happen too fast, so meanwhile, users would need to use the zoom.&lt;/p&gt;
&lt;h3 id=&quot;action-item-for-mobile-zooming&quot;&gt;Action Item for Mobile Zooming&lt;/h3&gt;
&lt;p&gt;Open your site or app on a mobile device. Try zooming in with your fingers. &lt;strong&gt;Can you do it?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;captions-and-transcripts&quot;&gt;Captions and Transcripts&lt;/h2&gt;
&lt;h3 id=&quot;captions&quot;&gt;Captions&lt;/h3&gt;
&lt;p&gt;Imagine if you couldn&amp;#39;t hear the words said in the video. How would you then understand what is going on in that video? It can happen for numerous reasons. You could have a hearing-related disability or be in a crowded place where you don&amp;#39;t want to turn the sound on.&lt;/p&gt;
&lt;p&gt;For these situations, captions are essential. Captions are a bit different from subtitles. They also contain information about important sounds on the video, and they identify the speaker if they&amp;#39;re not easy to identify from the video. Captions can be either &lt;em&gt;closed&lt;/em&gt;, meaning they can be turned on, or &lt;em&gt;open&lt;/em&gt;, meaning they are always present. &lt;/p&gt;
&lt;p&gt;And a word about auto-captions: Please, no. The speech recognition algorithms aren&amp;#39;t that good yet, and they produce lousy quality. Some even call them &amp;quot;auto-craptions.&amp;quot; Suppose you don&amp;#39;t have a hearing-related disability and haven&amp;#39;t encountered this in your life. In that case, I have another example that could maybe give some context: Services like Netflix and subtitles (when the subtitle language is not English). I mean, at least for Finnish, the subtitles are often just crap. The reason is either using automated translations or not paying enough for professionals to do the translations.&lt;/p&gt;
&lt;h3 id=&quot;transcripts&quot;&gt;Transcripts&lt;/h3&gt;
&lt;p&gt;Transcripts are a way for DeafBlind users to understand what&amp;#39;s going on in videos or audio content (say, podcasts). Also, for audio-only content, transcripts are the only way for Deaf people to get the message. &lt;/p&gt;
&lt;p&gt;If you&amp;#39;re interested to learn more about captions, transcripts, and audio descriptions (which I&amp;#39;m not covering here), &lt;a href=&quot;https://webaim.org/techniques/captions/&quot;&gt;WebAIM: Captions, Transcripts, and Audio Descriptions&lt;/a&gt; is a good resource on that.&lt;/p&gt;
&lt;h3 id=&quot;action-item-for-captions-and-transcripts&quot;&gt;Action Item for Captions and Transcripts&lt;/h3&gt;
&lt;p&gt;Go through every piece of multimedia on your website. &lt;strong&gt;Are the captions and transcripts in place (depending on the media type)?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, I have introduced seven checks any developer can do to ensure that their website is a bit more accessible. Some of them take more time than others, but they&amp;#39;re worth it!&lt;/p&gt;
&lt;p&gt;Do you have any tips for simple checks for accessibility problems that aren&amp;#39;t easy to catch with automated tests?&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_generalnav&quot;&gt;WAI-ARIA Authoring Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webaim.org/techniques/alttext/&quot;&gt;WebAIM: Alternative Text&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webaim.org/techniques/captions/&quot;&gt;WebAIM: Captions, Transcripts, and Audio Descriptions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=wIj-NymT5fY&quot;&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;-simulation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chrispederick.com/work/web-developer/&quot;&gt;Web Developer-extension&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href=&quot;https://unsplash.com/@glenncarstenspeters?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Glenn Carstens-Peters&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Why I&#39;m Not One of the Guys</title>
    <link href="https://eevis.codes/blog/2021-07-24/why-im-not-one-of-the-guys/" />
    <updated>2023-01-03T08:57:49.014Z</updated>
    <id>https://eevis.codes/blog/2021-07-24/why-im-not-one-of-the-guys/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3QKpt2Hjkbsrcxj5rTNLio/001a1f82eb12578e613ebcd0e1101825/etienne-girardet-j1YEM-Fq9xM-unsplash.jpg"/>]]>
      &lt;p&gt;&lt;em&gt;Spanish translation by &lt;a href=&quot;https://www.ibidem-translations.com/&quot;&gt;Ibidem&lt;/a&gt;: &lt;a href=&quot;https://www.ibidem-translations.com/edu/traduccion-lenguaje-no-sexista/&quot;&gt;Por qué no soy “uno de los chicos”
&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&amp;quot;Hey, guys&amp;quot; is a phrase I often hear.  It usually continues with words like &amp;quot;what do you think about this?&amp;quot; And if the context is within a mixed-gender group, I feel left out. I feel like they&amp;#39;re talking to everyone else (or, at least, all who identify as a man) but me. &lt;/p&gt;
&lt;p&gt;I know many native English speakers use the phrase as gender-neutral. 
But it is not, and in this blog post, I will discuss the problems of using that expression. I will also address some other aspects of the &amp;quot;man-default.&amp;quot; One of the things it means is, for example, that if we are talking about a developer, that developer is often referred to as &amp;quot;he&amp;quot; (so, &amp;quot;A developer found a bug. He started fixing it&amp;quot;).&lt;/p&gt;
&lt;p&gt;This is a very personal topic for me, as I&amp;#39;ve had to fight for my spot as a developer, and I&amp;#39;m still facing these assumptions that I&amp;#39;m less of a professional because of my gender. So if you don&amp;#39;t recognize the problem, please just believe me and don&amp;#39;t start mansplaining how, for example, &amp;quot;you guys&amp;quot; is a gender-neutral term, and I should just suck it up. Please just don&amp;#39;t. &lt;/p&gt;
&lt;h2 id=&quot;the-problem-of-man-default&quot;&gt;The Problem of Man-Default&lt;/h2&gt;
&lt;p&gt;When you refer to a hypothetical person whose gender is unknown, what is the pronoun you use? If the answer is &amp;quot;he,&amp;quot; then congrats, you&amp;#39;ve found the man-default. It means that you default to man when talking about someone whose gender you don&amp;#39;t know. &lt;/p&gt;
&lt;p&gt;Man-default can also be found in expressions such as &amp;quot;manning the station,&amp;quot; &amp;quot;man-hours,&amp;quot; or &amp;quot;chairman.&amp;quot; It is visible in other languages as well. For example, in Finnish, despite having only one personal pronoun for all genders, we have words such as &amp;quot;lakimies&amp;quot; (=lawyer, literal translation would be &amp;quot;law man&amp;quot;), &amp;quot;palomies&amp;quot; (fireman), or &amp;quot;miehittää&amp;quot; (same as in &amp;quot;manning the station&amp;quot;). &lt;/p&gt;
&lt;p&gt;Another example of man-default comes from sports. Often men&amp;#39;s league is referred to as &amp;quot;the league,&amp;quot; and then there is the women&amp;#39;s league with the word &amp;quot;women&amp;quot; in it. For example, we have the NBA (National Basketball Association) and WNBA (Women&amp;#39;s National Basketball Association). It displays men as default and women as &amp;quot;other,&amp;quot; something that needs an explanation. &lt;/p&gt;
&lt;p&gt;But you know, the language we use shapes the reality around us. If we always speak about men doing something and especially default to men when talking about fields where men are the majority, we maintain those structures and keep the image up. &lt;/p&gt;
&lt;p&gt;Ok, this might sound a bit abstract, but let me try to explain it a bit further: If we always refer to software developers as &amp;quot;he,&amp;quot; we (unconsciously) build an image where all software developers are men. It leads to situations where men are seen as better developers, because hey, of course. And then women are seen as less of professionals because they aren&amp;#39;t &lt;em&gt;men&lt;/em&gt; who are the default. And this is how unconscious bias, which leads to discrimination, builds. &lt;/p&gt;
&lt;p&gt;It is also a topic that has become visible with translation algorithms. Nicolas Kayser-Bril wrote &lt;a href=&quot;https://algorithmwatch.org/en/google-translate-gender-bias/&quot;&gt;a piece for Algorithm Watch&lt;/a&gt; about how Google Translate systematically changes the gender for translations when the initial gender doesn&amp;#39;t fit the stereotypes. &lt;/p&gt;
&lt;p&gt;There was also an interesting &lt;a href=&quot;https://twitter.com/vuokko/status/1369185483683733505?s=20&quot;&gt;Twitter-thread about Google Translate and pronouns&lt;/a&gt; during the International Women&amp;#39;s Day in 2021: &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/vuokko/status/1369185483683733505?s=20&quot;&gt;https://twitter.com/vuokko/status/1369185483683733505?s=20&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These examples provide some fascinating insight into how language shapes reality. These translations are a direct consequence of the data used to train these algorithms. We, humans, are the same - if the data we get defaults always to, for example, men being the developers, we tend to believe that being a developer is meant only for men.&lt;/p&gt;
&lt;h2 id=&quot;guys-is-not-gender-neutral&quot;&gt;Guys is Not Gender Neutral&lt;/h2&gt;
&lt;p&gt;Back to the term &amp;quot;guys.&amp;quot; Some argue that it is a gender-neutral term. Well, it is not. It originates from the word &amp;quot;guy,&amp;quot; which is singular and means a man. Yes, I know, you might have learned it and been using it believing it is gender-neutral. That is ok - you didn&amp;#39;t know. You are not a bad person. But could you please remember from now on that there are people who feel excluded when someone uses the word? Thank you!&lt;/p&gt;
&lt;p&gt;Sara Bent from Hotjar writes about how they had a &lt;a href=&quot;https://www.hotjar.com/blog/gender-inclusive-language-workplace/&quot;&gt;&amp;quot;Guys Jar&amp;quot;-challenge&lt;/a&gt;, a version of a swear jar. In their version, every time someone participating used the word &amp;#39;guys&amp;#39; to refer to a group of mixed (or only-women) people, they had to pay. &lt;/p&gt;
&lt;p&gt;I like how Sara puts the idea of using &amp;quot;guys&amp;quot; (from the blog post linked above):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Even though most people who use the term don&amp;#39;t do so with the intent of it being sexist or exclusive of women, &lt;strong&gt;it can and often does cause women to feel left out of the conversation.&lt;/strong&gt; Imagine you used &amp;#39;gals&amp;#39; to refer to a room full of men and women—do you think the men would respond?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;#39;ve had and read many conversations with people (often with cis-men) about why they think &amp;quot;guys&amp;quot; is a gender-neutral term. One of the &amp;quot;best&amp;quot; arguments I&amp;#39;ve heard so far is that &amp;quot;we can&amp;#39;t change the way soccer-moms in suburbs in the States use this term, so using it is ok for us as well.&amp;quot; Well yeah, one thing is correct; we probably can&amp;#39;t change that. &lt;/p&gt;
&lt;p&gt;But you know, the conversation where they said this wasn&amp;#39;t anywhere near those soccer moms - it was in Finland, in a professional context where English is the language used. And in a group where someone had just said they feel excluded when others use the word &amp;quot;guys&amp;quot; when referring to them. It was about the language used in the said setting. That felt... absurd.&lt;/p&gt;
&lt;h2 id=&quot;well-why-dont-i-just-speak-up&quot;&gt;Well, Why Don&amp;#39;t I Just Speak Up?&lt;/h2&gt;
&lt;p&gt;I don&amp;#39;t know about you, but when I&amp;#39;m in the minority, and I&amp;#39;ve seen that raising these issues sometimes gets even aggressive responses, it is hard to speak up. &lt;/p&gt;
&lt;p&gt;Another thing that makes speaking up hard is that when I do, people usually assume I&amp;#39;m criticizing them, not their actions. These people tend to feel insulted, and you know how people are when they feel humiliated. The focus shifts to &lt;em&gt;their&lt;/em&gt; feelings and me trying to tell them that no, I&amp;#39;m not assuming you&amp;#39;re a terrible person. I&amp;#39;m only trying to bring to the attention things that you probably unconsciously are doing. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve had good examples of when people have listened when I&amp;#39;ve brought this issue to attention. Still, more often, it ends up in an endless conversation about soccer moms and other non-relevant arguments. It gets tiring very soon.&lt;/p&gt;
&lt;p&gt;And you know... It shouldn&amp;#39;t be the responsibility of the minority to try to fix these issues alone. If it&amp;#39;s something that has been brought up in a community before, the majority has the responsibility to act. &lt;/p&gt;
&lt;h2 id=&quot;what-can-i-use-then&quot;&gt;What Can I Use, Then?&lt;/h2&gt;
&lt;p&gt;So, you&amp;#39;ve decided to change your vocabulary. Thank you! I really appreciate that! You might be wondering what to use then. Here are some alternatives and considerations.&lt;/p&gt;
&lt;h3 id=&quot;alternatives-for-guys&quot;&gt;Alternatives for Guys&lt;/h3&gt;
&lt;p&gt;There are multiple alternatives you can use instead of &amp;quot;hey guys.&amp;quot; Here are some examples with &lt;a href=&quot;https://twitter.com/krees/status/1106617681116045312?s=20&quot;&gt;Kim Rees&amp;#39; tweet&lt;/a&gt; about some options:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/krees/status/1106617681116045312?s=20&quot;&gt;https://twitter.com/krees/status/1106617681116045312?s=20&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;using-they-instead-of-he-or-she-for-that-matter&quot;&gt;Using They Instead of He (or She for That Matter)&lt;/h3&gt;
&lt;p&gt;When talking about a person whose gender you don&amp;#39;t know - wheater a real person, or a hypothetical person (such as a hypothetical developer), default to &amp;quot;they&amp;quot;-pronoun. &lt;/p&gt;
&lt;p&gt;It might feel a bit weird at first, but I promise you, you&amp;#39;ll get used to it. I speak from experience :) &lt;/p&gt;
&lt;h3 id=&quot;being-aware-of-man-default&quot;&gt;Being Aware of Man-Default&lt;/h3&gt;
&lt;p&gt;One more thing I want to point out is being aware of the man-default. Start paying attention to the words you use; do they keep up the picture of a man being the default? Yes, it&amp;#39;s a challenging task to do because we use language unconsciously and automatically. But you can try anyway.&lt;/p&gt;
&lt;p&gt;There are usually gender-neutral words to use instead of the ones defaulting to man. Here are some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fireman -&amp;gt; Firefighter&lt;/li&gt;
&lt;li&gt;Chairman -&amp;gt; Chairperson&lt;/li&gt;
&lt;li&gt;Freshman -&amp;gt; First-year student&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And so on. The internet is full of examples, so go and educate yourself!&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So, TLDR; &amp;quot;Hey guys&amp;quot; is not a gender-neutral expression, and there are lots of people who feel excluded when someone uses it to refer to a group where they belong. I&amp;#39;m one of them. &lt;/p&gt;
&lt;p&gt;There are better alternatives for the expression, and we should use them. Also, using &amp;quot;he&amp;quot; as a default pronoun is problematic, so let&amp;#39;s use &amp;quot;they.&amp;quot; And let&amp;#39;s be aware of the fact that man is seen as a default in many places, so let&amp;#39;s pay attention to the words we use.&lt;/p&gt;
&lt;p&gt;You can find more examples of non-sexist language in &lt;a href=&quot;https://geekfeminism.wikia.org/wiki/Nonsexist_language&quot;&gt;Geek Feminism Wiki&amp;#39;s guide on non-sexist language&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href=&quot;https://unsplash.com/@etiennegirardet?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Etienne Girardet&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>The Dark Side of Blogging</title>
    <link href="https://eevis.codes/blog/2021-09-09/the-dark-side-of-blogging/" />
    <updated>2023-01-03T08:57:48.447Z</updated>
    <id>https://eevis.codes/blog/2021-09-09/the-dark-side-of-blogging/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/65Xvi26ULl2ll2BRwvFmuk/9dce47b5a8dfdad9510f9ba310ade4c7/marco-bianchetti-jkfxmKswsXY-unsplash.jpg"/>]]>
      &lt;p&gt;Lately, I&amp;#39;ve seen multiple excellent posts about why developers should blog. They list very good reasons, and I wholeheartedly agree with them. Blogging is good for many things, it&amp;#39;s fun, and you learn a lot during the process. &lt;/p&gt;
&lt;p&gt;However, there are downsides to blogging as well, especially if you write about controversial topics. This blog post will discuss some of these downsides, tell my own experiences, and share tips on conquering those not-so-great things. I&amp;#39;m writing the tips for myself as advice I would have needed to hear, so they might not all apply to you, but I hope you&amp;#39;ll get at least something out of this post!&lt;/p&gt;
&lt;h2 id=&quot;some-background&quot;&gt;Some Background&lt;/h2&gt;
&lt;p&gt;I started actively blogging about a year ago. My motivator was, at first, to write blog posts 16 weeks in a row to get the 16-weeks badge from &lt;a href=&quot;https://dev.to/&quot;&gt;Dev.to&lt;/a&gt;. I earned the badge, and if you&amp;#39;re interested, I wrote &lt;a href=&quot;https://dev.to/eevajonnapanula/checkpoint-16-weeks-of-blogging-2ofj&quot;&gt;a blog post about what I learned from that journey.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Blogging is something I like a lot. It has given me a way to construct my thoughts through writing and learn in the process. I already have a good archive of articles I&amp;#39;ve written. It hasn&amp;#39;t been once or twice that I&amp;#39;ve had the opportunity to refer to one of my blog posts in a conversation.&lt;/p&gt;
&lt;p&gt;However, as much I like this, there have been downsides as well. And during the past year, I&amp;#39;ve been struggling with stuff - as I would imagine most of us have because of Covid and all the isolation it causes. I&amp;#39;m also recovering from a brain injury, which makes me prone to fatigue. So these things have significantly slowed my speed of writing, especially in 2021. &lt;/p&gt;
&lt;p&gt;So, let&amp;#39;s have a look at the downsides and some advice!&lt;/p&gt;
&lt;h2 id=&quot;writing-quality-content-takes-time&quot;&gt;Writing Quality Content Takes Time&lt;/h2&gt;
&lt;p&gt;If you want to write some quality content, it takes time. Suppose you&amp;#39;re creating a tutorial of some sort, building the end result, testing it, and writing the blog post. It can take a long time from the start to publishing it. &lt;/p&gt;
&lt;p&gt;In Dev, I&amp;#39;ve come across multiple posts that are basically just (short) lists of links. There is time and place for those lists, but at least I like to read longer posts with some actual content, and from the discussions I&amp;#39;ve seen in Dev, I&amp;#39;m not the only one. And writing longer posts takes more time. &lt;/p&gt;
&lt;p&gt;Editing is another part of the writing process that requires time. Of course, it is possible to just write and then publish. But parts of writing &lt;em&gt;quality&lt;/em&gt; content are editing, proofreading, and all those moments when you might need to delete half of your blog post and start over. &lt;/p&gt;
&lt;p&gt;So, my advice for this would be to &lt;strong&gt;give yourself time&lt;/strong&gt;. In life, all kinds of things can happen. It is okay if you don&amp;#39;t write every week, especially if you&amp;#39;re writing for yourself and not, for example, as a job. And when you do have time and energy, purposely &lt;strong&gt;book time for writing, editing, and all that&lt;/strong&gt;. &lt;/p&gt;
&lt;h2 id=&quot;the-pressure&quot;&gt;The Pressure&lt;/h2&gt;
&lt;p&gt;You also might feel pressured to write and publish. For some, this is not an issue, but at least for me, it has been. When I was blogging for 16 weeks, publishing a blog post a week, I often felt pressure in the back of my mind. &lt;/p&gt;
&lt;p&gt;Okay, it was me wanting to keep the streak going, and nobody else was asking me to do anything. Nevertheless, it really stressed me out sometimes. After the 16 weeks passed and I got my Dev-badge, the publishing pace slowed significantly down until, in the summer, I&amp;#39;ve published maybe a couple of posts. Well, depending on how you define &amp;quot;summer.&amp;quot;&lt;/p&gt;
&lt;p&gt;It&amp;#39;s also possible that the pressure comes from outside; if you write for a company or a commissioned piece, then it&amp;#39;s not just you anymore. There are deadlines, and someone else is depending on you. &lt;/p&gt;
&lt;p&gt;As mentioned, there was no one else pressuring me on this, at least on purpose. I&amp;#39;m the one who makes me feel like I need to write. Sometimes I don&amp;#39;t even know why; there is just that nagging feeling that I haven&amp;#39;t published anything in ages. &lt;/p&gt;
&lt;p&gt;There is another kind of pressure as well: writing about specific topics. I mean, my blog posts have been mostly about accessibility topics, but there have been some other themes as well. Still, sometimes I feel like I need to &amp;quot;stay in my lane.&amp;quot; Now that I think of it, I&amp;#39;ve actually been told that when writing about equality-related topics. But I think there&amp;#39;s richness in being able to explore different themes.&lt;/p&gt;
&lt;p&gt;So what kind of advice would I give to combat the pressure of writing? First of all, &lt;strong&gt;be merciful and compassionate to yourself&lt;/strong&gt;. No one&amp;#39;s life is depending on your writing, and it is okay to take your time. And &lt;strong&gt;it is also okay to branch out and write about other topics as well&lt;/strong&gt;. These might feel like a bit obvious pieces of advice, but at least I need to hear them once in a while. &lt;/p&gt;
&lt;h2 id=&quot;responses-to-controversial-topics&quot;&gt;Responses to Controversial Topics&lt;/h2&gt;
&lt;p&gt;As I mentioned, I&amp;#39;ve been mostly writing about accessibility and front-end development. Once in a while, I&amp;#39;ve thrown in some posts related to controversial topics. Good examples of these are language and being a woman in an industry where women haven&amp;#39;t had space for a long time. &lt;/p&gt;
&lt;p&gt;Recently I wrote about why I don&amp;#39;t want to be referred to with the phrase &amp;quot;you guys.&amp;quot; First, I shared it on LinkedIn, and the response I got was pretty much supportive, with a couple of not-so-supportive comments. I thought, okay, &amp;quot;let&amp;#39;s publish this on Dev.&amp;quot; I wasn&amp;#39;t prepared for the flood of comments it received.&lt;/p&gt;
&lt;p&gt;In the comments, there were some encouraging comments, and then some comments with good critique and conversation. But then the negative comments started coming, and I felt paralyzed. I wanted to answer the ones with encouragement and good commentary, but the negativity just drained me. So I want to apologize to anyone who was waiting for my answer and never got it! I had to draw a line to protect my mental health.&lt;/p&gt;
&lt;p&gt;Some of the negative comments were clear trolls; some of them clearly tried, but it felt like they hadn&amp;#39;t read anything more but the title of the blog post. And the mansplaining. Oh, the mansplaining! For those who are offended by the word, I do not mean that all opposing views were mansplaining. As said, some good comments challenged my points, and they definitely don&amp;#39;t fall under the term mansplaining. But then again, some comments definitely were mansplaining. &lt;/p&gt;
&lt;p&gt;As said, those comments drained me. I was contemplating removing the whole post from Dev, but in the end, two things kept me from deleting it. First was all the encouraging comments I received in the post, and the second was the support I got from the Dev&amp;#39;s team (Thank you again, Michael, for reaching out! It meant a lot!).&lt;/p&gt;
&lt;p&gt;So what I learned from this, and what tips could I give? First, suppose you write about controversial topics and receive a similar reception. In that case, &lt;strong&gt;it is totally okay not to answer the comments&lt;/strong&gt;. You and your health come first. And &lt;strong&gt;it&amp;#39;s okay to draw lines and keep them&lt;/strong&gt;. You don&amp;#39;t owe anything to anyone. This is especially true for people from a minority, as they tend to get more trolling and negative comments just because of their background. &lt;/p&gt;
&lt;h2 id=&quot;summing-up-the-tips&quot;&gt;Summing Up The Tips&lt;/h2&gt;
&lt;p&gt;So, to sum up, what I&amp;#39;ve been discussing, here are the tips from the previous sections:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Give yourself time. &lt;/li&gt;
&lt;li&gt;Book time for the writing process.&lt;/li&gt;
&lt;li&gt;Be merciful to yourself. &lt;/li&gt;
&lt;li&gt;It&amp;#39;s okay not to answer all comments.&lt;/li&gt;
&lt;li&gt;Draw lines, and keep them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href=&quot;https://unsplash.com/@marcobian?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Marco Bianchetti&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Making Better Pull Requests</title>
    <link href="https://eevis.codes/blog/2021-10-18/making-better-pull-requests/" />
    <updated>2023-01-03T08:57:48.048Z</updated>
    <id>https://eevis.codes/blog/2021-10-18/making-better-pull-requests/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3dXHS6tm3G1FFRTnarWKfn/c681f33b819c1768fa4324aa92b4970d/thisisengineering-raeng-iQqRM0XJvn8-unsplash.jpg"/>]]>
      &lt;p&gt;Hactoberfest is here! Time to find those issues and participating repositories, write some code, and create pull requests! I&amp;#39;ve been eagerly waiting for October, mostly because of Hacktoberfest. &lt;/p&gt;
&lt;p&gt;Last year, I participated in Hacktoberfest by being a contributor and a maintainer. If you&amp;#39;re interested, you can read about my blog post about last year&amp;#39;s experiences: 
&lt;a href=&quot;https://dev.to/eevajonnapanula/story-of-an-accidental-open-source-maintainer-6jh&quot;&gt;Eevis Panula - Story of an accidental open source maintainer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This year, I decided to take it a bit easier. I mean, as mentioned in the blog post, last year we bought a house and were moving during October. Another challenging thing from last year was the maintainer part. I loved it, but it really, really stressed me out. So I&amp;#39;m not going to do it this year. And there&amp;#39;s another piece to the puzzle this year as well: I&amp;#39;m switching jobs, so that&amp;#39;s another reason to take it easy during this Hacktoberfest.&lt;/p&gt;
&lt;p&gt;One big part of Hacktoberfest is creating pull requests. I remember the first time I made a PR in an open-source repository. I didn&amp;#39;t know the repository&amp;#39;s maintainers, and it didn&amp;#39;t have any guidelines for pull requests, such as a pull request template. I had no idea what the maintainer expected from a PR.  I felt super anxious. &lt;/p&gt;
&lt;p&gt;If I had known back then what I know now, it would&amp;#39;ve been easier for me. So that&amp;#39;s why in this blog post, I&amp;#39;m sharing some tips on how to make better PRs. These are the best practices I&amp;#39;ve picked up along the way. However, note that there might be some other conventions in the project you&amp;#39;re contributing. Be sure to check them. There might be a &lt;a href=&quot;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/about-issue-and-pull-request-templates&quot;&gt;pull request template&lt;/a&gt; or contribution guidelines. &lt;/p&gt;
&lt;p&gt;The tips I&amp;#39;m going to give are the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2021-10-18/making-better-pull-requests/#keep-the-pull-request-in-scope&quot;&gt;Keep the pull request in scope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2021-10-18/making-better-pull-requests/#give-context-to-the-reviewer&quot;&gt;Give context to the reviewer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2021-10-18/making-better-pull-requests/#add-a-picture-of-the-changes&quot;&gt;Add a picture of the changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2021-10-18/making-better-pull-requests/#pull-request-is-a-place-for-discussion&quot;&gt;Pull request is a place for discussion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;keep-the-pull-request-in-scope&quot;&gt;Keep the Pull Request in Scope&lt;/h2&gt;
&lt;p&gt;One reason for keeping the PR in scope is that smaller PRs are always easier to review than those with lots of changes. Sometimes, of course, the feature you are working on is a big one, and thus the PR gets enormous. However, keeping in the scope in those cases means that if you find things to fix or improve, I would suggest creating a separate PR if they&amp;#39;re not part of the feature.  &lt;/p&gt;
&lt;p&gt;Sometimes it would be good to separate the feature into smaller PRs. The scope gets smaller, and thus there will be less code to review. In these cases, I would advise splitting the PR into logical parts. For example, if you are building an app for period tracking, and the feature you&amp;#39;re implementing is a calendar view where the user adds their data, there are multiple ways to split that. &lt;/p&gt;
&lt;p&gt;Maybe the first part would be creating the endpoint to the backend and then adding a simple way to input the data with just input fields (so, no calendar yet). Maybe you could combine these all into a feature branch in the end. &lt;/p&gt;
&lt;p&gt;Whatever you decide to do, be sure to check what the conventions are in the project you&amp;#39;re working on. If you&amp;#39;re working with a team, you can reach out and talk with them. There might also be a document about contributing, where there might be something about this topic.&lt;/p&gt;
&lt;h2 id=&quot;give-context-to-the-reviewer&quot;&gt;Give Context to the Reviewer&lt;/h2&gt;
&lt;p&gt;When I&amp;#39;m reviewing a pull request, I love seeing some context about the changes. It might be a link to an issue (with more info than a vague title), or it might be a text explaining the context in the PR description. Anyway, having that context helps me get into reviewing mode and know what I&amp;#39;m reviewing.&lt;/p&gt;
&lt;p&gt;Continuing from the example from the previous section, I would briefly write about the calendar view as a goal or link to the issue and then explain what I did in the pull request. I would also add some notes about the decision to split the feature and maybe some words about what&amp;#39;s not yet included.&lt;/p&gt;
&lt;h2 id=&quot;add-a-picture-of-the-changes&quot;&gt;Add a Picture of the Changes&lt;/h2&gt;
&lt;p&gt;When the pull request is about changing something in the UI, I love seeing a picture of the changes. It&amp;#39;s part of the context, and I know what to look for when I pull the branch and test it. &lt;/p&gt;
&lt;p&gt;Continuing with the example, after moving on to making changes to the UI, I would add screenshots of those changes to the pull request. I might consider adding a before picture when finally implementing the calendar view to see how the feature evolves. That, however, would depend on the situation. &lt;/p&gt;
&lt;h2 id=&quot;pull-request-is-a-place-for-discussion&quot;&gt;Pull Request is a Place for Discussion&lt;/h2&gt;
&lt;p&gt;One piece of advice I want to give here is that pull requests are places for discussion.  It&amp;#39;s ok to submit a pull request with questions (you could use a draft pull request in Github for that). On the other hand, you&amp;#39;ll likely get questions and comments. Maybe even request to change something. When you do, and if they&amp;#39;re not clear, it&amp;#39;s always ok to ask for clarification or the reasons for change requests. &lt;/p&gt;
&lt;p&gt;Why am I pointing this out? Well, at the beginning of my career, I felt like all the things said were God&amp;#39;s truth, and I, as a newbie developer, didn&amp;#39;t have any place to ask or question anything. That was even if the reviewers were friendly and tried to facilitate an atmosphere where that would&amp;#39;ve been ok. I would&amp;#39;ve needed to hear this advice many more times back then for it to sink in. &lt;/p&gt;
&lt;h2 id=&quot;summing-up&quot;&gt;Summing Up&lt;/h2&gt;
&lt;p&gt;In this blog post, I&amp;#39;ve discussed pull requests and how to make them better. The advice is subjective, and it&amp;#39;s based on my own experiences, and you might have other opinions. That&amp;#39;s ok. &lt;/p&gt;
&lt;p&gt;I pointed out that it would be good to keep the pull request in scope and try not to do everything in one pull request. If you want to make some additional changes, I&amp;#39;d suggest opening a separate PR for that. Also, in the pull request description, give context about the pull request for the reviewer. Additionally, if there are changes in the UI, I suggest adding a screenshot of those changes. The last thing I pointed out is that pull requests are places for discussion.&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/eevajonnapanula/story-of-an-accidental-open-source-maintainer-6jh&quot;&gt;Eevis Panula - Story of an accidental open source maintainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/about-issue-and-pull-request-templates&quot;&gt;Github - Pull request template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href=&quot;https://unsplash.com/@thisisengineering?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;ThisisEngineering RAEng&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/@thisisengineering?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Thoughts From Reading Demystifying Disability by Emily Ladau</title>
    <link href="https://eevis.codes/blog/2021-11-06/thoughts-from-reading-demystifying-disability-by-emily-ladau/" />
    <updated>2023-01-03T08:57:47.277Z</updated>
    <id>https://eevis.codes/blog/2021-11-06/thoughts-from-reading-demystifying-disability-by-emily-ladau/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2FZlDcn2ESZMvM6IwXBekk/f7370ce6e91a15d2ed3c08d7751ff307/Twitter_post_-_2large-twitter-reducing-motion.png"/>]]>
      &lt;p&gt;I finished reading &lt;a href=&quot;https://emilyladau.com/book/&quot;&gt;Demystifying Disability by Emily Ladau&lt;/a&gt; last week. It&amp;#39;s a fantastic book discussing disability, ableism, accessibility, disability etiquette, and many more things. I highly recommend buying and reading or listening to it!&lt;/p&gt;
&lt;p&gt;I have so many thoughts from reading the book, and I want to share some of them with you. And I know many more will come, as I often process new things over the course of a longer time. &lt;/p&gt;
&lt;h2 id=&quot;person-first-language-vs-identity-first-language&quot;&gt;Person-First Language vs. Identity-First Language&lt;/h2&gt;
&lt;p&gt;So, for those unfamiliar with the terms, person-first language (PFL) means using phrases where the person comes first. So, for example, &lt;strong&gt;people with disabilities&lt;/strong&gt;. On the other hand, identity-first language (IFL) recognizes that disability is part of the person&amp;#39;s identity, such as &lt;strong&gt;Autistic people&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;Emily Ladau writes about her experiences with these approaches:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Truth to be told, I went kind of overboard with my IFL evangelism at first. I could not understand why anyone would prefer PFL and pushed against it whenever I had the chance. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I could say I was the opposite. At some point, it was difficult for me to understand why someone would like to go with the identity-first approach. Okay, I knew the reasons, but I could say I did not truly understand it. But now I do, and even better because of reading Demystifying Disability. &lt;/p&gt;
&lt;p&gt;The book also got me thinking about why I&amp;#39;ve been feeling this way. For those of you who don&amp;#39;t know, I had a concussion a few years back. I&amp;#39;m still recovering from it, and I know that things won&amp;#39;t go back to what they were before that hit. &lt;/p&gt;
&lt;p&gt;I was 26 when that happened, so I had a pretty strong sense of who I was before that. One thing was that I had an extraordinary memory. I could recall details so well that people around me found it strange sometimes.&lt;/p&gt;
&lt;p&gt;And that was a big part of my identity. After the concussion, I realized I have problems with my memory, and I&amp;#39;ve had to come a long way to actually accept that this is my life now and I&amp;#39;m not the same person as I used to be back then. &lt;/p&gt;
&lt;p&gt;But I&amp;#39;m guessing that this is one of the reasons I prefer to refer myself with the person-first language. I am still working with my identity because of the significant changes (memory is not the only one), and I assume I still will be for a long time. And that is okay.&lt;/p&gt;
&lt;p&gt;As a final thought to this part, I want to remind you that it&amp;#39;s a personal choice, and if someone prefers either one of these approaches, please do respect that. &lt;/p&gt;
&lt;h2 id=&quot;ableism&quot;&gt;Ableism&lt;/h2&gt;
&lt;p&gt;Okay, let&amp;#39;s start again with some definitions. How Emily Ladau defines ableism:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ableism is attitudes, actions, and circumstances that devalue people because they are disabled or perceived as having a disability.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ableism is well-woven into our society, and we all echo these ableist biases and beliefs. And as with any type of systemic discrimination, it&amp;#39;s hard to see if you&amp;#39;re not part of the group that society discriminates. &lt;/p&gt;
&lt;p&gt;I want to discuss two things related to ableism; systemic ableism and that people with disabilities can be ableist as well. &lt;/p&gt;
&lt;h3 id=&quot;systemic-ableism&quot;&gt;Systemic Ableism&lt;/h3&gt;
&lt;p&gt;Emily Ladau brings up the fact that ableism is a self-perpetuating cycle. I can fully agree with this and give an example from conversations I&amp;#39;ve had with people.  &lt;/p&gt;
&lt;p&gt;I often hear that the reason for not having accessible offices or using accessible apps and services in work environments is that &amp;quot;we don&amp;#39;t have any people with disabilities working in our company.&amp;quot; First, you wouldn&amp;#39;t know, as people don&amp;#39;t always want to disclose their disability statuses. &lt;/p&gt;
&lt;p&gt;And then there&amp;#39;s this: If your workplace is not accessible, how can a disabled person even come to an interview? If the applications or interviewing techniques you use for interviewing aren&amp;#39;t accessible, how do you think you will be even able to hire someone who has barriers already before starting?  &lt;/p&gt;
&lt;p&gt;Again, Emily Ladau writes it well, saying that ableist assumptions lead to systemic ableism, which in turn leads to discrimination, despite being unintentional. &lt;/p&gt;
&lt;h3 id=&quot;people-with-disabilities-can-be-ableist-too&quot;&gt;People with Disabilities Can Be Ableist Too&lt;/h3&gt;
&lt;p&gt;I love how Emily Ladau points out that there is &amp;quot;no magical force field preventing disabled people being ableist.&amp;quot; That is so true. Being a person with a disability doesn&amp;#39;t mean I couldn&amp;#39;t be ableist as well. &lt;/p&gt;
&lt;p&gt;Even though I&amp;#39;m an accessibility specialist and disabled person myself, I still catch myself having these ableist thoughts. And I&amp;#39;ve definitely done some ableist things. But the thing is, we all do. From now on, we need to recognize these thoughts and actions. We need to try to be better and apologize if it&amp;#39;s possible in that situation.&lt;/p&gt;
&lt;h2 id=&quot;inspiration-porn&quot;&gt;Inspiration Porn&lt;/h2&gt;
&lt;p&gt;Emily Ladau defines inspiration porn with the following words:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[Inspiration porn] is an accurate way to describe the concept of how disabled people and their stories are objectified by the media to make observers feel warm and fuzzy or better about themselves.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Late Stella Young popularized this term, and below, you can find her TEDx talk &amp;quot;I&amp;#39;m not your inspiration, thank you very much.&amp;quot; Here&amp;#39;s also &lt;a href=&quot;http://www.humber.ca/makingaccessiblemedia/modules/01/transript/I&amp;#39;m_Not%20_Your_Inspirations_transcript.pdf&quot;&gt;a link to the transcript of the talk&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=8K9Gg164Bsw&quot;&gt;https://www.youtube.com/watch?v=8K9Gg164Bsw&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I think that talk is so on the point of the problems of inspiration porn. Why are disabled people seen as inspirational just because they get up in the morning and get on with their days? Would you say someone without a disability is inspirational because of that? &lt;/p&gt;
&lt;p&gt;I often pass as a nondisabled person because my disability is invisible. And truth to be told, one reason for hiding it&amp;#39;s that I&amp;#39;m a bit afraid that I  would be seen as inspirational for doing something that anyone else would do despite having a disability. &lt;/p&gt;
&lt;p&gt;In her &lt;a href=&quot;https://www.youtube.com/watch?v=g3EYc98Z0ug&quot;&gt;axe-con-talk&lt;/a&gt;, Haben Girma speaks about using the word &amp;quot;inspired&amp;quot; in the sense that what you are inspired to do when someone is inspiring. That&amp;#39;s totally something I can get on board with. &lt;/p&gt;
&lt;p&gt;So, when we feel like someone is inspiring us, let&amp;#39;s think about why that is. Is it because seeing someone with a disability makes us feel better about ourselves, in a way that we believe that we are better than them? Or does it make us work towards an accessible world for all? &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;I love Demystifying Disability and highly recommend it to everyone. It&amp;#39;s an excellent overview of disability, ableism, accessibility, and many other themes. &lt;/p&gt;
&lt;p&gt;In this blog post, I touched on only a couple of different themes in the book. As mentioned in the beginning, it gave me a lot to think about, and I believe there will be new realizations as I keep thinking about these themes.   &lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://emilyladau.com/book/&quot;&gt;Demystifying Disability by Emily Ladau&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=g3EYc98Z0ug&quot;&gt;Haben Girma - Difference drives innovation &amp;amp; Disability Inclusion benefits all of us&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=8K9Gg164Bsw&quot;&gt;Stella Young - I&amp;#39;m not your inspiration, thank you very much.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.humber.ca/makingaccessiblemedia/modules/01/transript/I&amp;#39;m_Not%20_Your_Inspirations_transcript.pdf&quot;&gt;Transcript of Stella Young&amp;#39;s talk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>First Month as an Accessibility Specialist at Oura</title>
    <link href="https://eevis.codes/blog/2021-11-22/first-month-as-an-accessibility-specialist-at-oura/" />
    <updated>2023-01-03T08:57:46.082Z</updated>
    <id>https://eevis.codes/blog/2021-11-22/first-month-as-an-accessibility-specialist-at-oura/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/4LmVbvywgg6sN1RZF0j0ME/532a4ad43e64011364f9980a2b9a2107/first-months-as-an-accessibility-specialist.png"/>]]>
      &lt;p&gt;So, I joined Oura Health in October, and it&amp;#39;s been a little bit over a month now. My role is an accessibility specialist, and I&amp;#39;m the first person to be hired in the company in such a role. &lt;/p&gt;
&lt;p&gt;During the past month, we&amp;#39;ve had busy times. We launched the new Gen3 ring, and it has brought a bustle. On the other hand, it&amp;#39;s been super cool to join the company during such a time. &lt;/p&gt;
&lt;p&gt;From an accessibility perspective, I know that we aren&amp;#39;t there yet. We have a long road to go to change things, but one thing I&amp;#39;m excited about is that there is a will to do that.&lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;ll share some initial thoughts and feelings from the past month. I&amp;#39;m super thrilled about what is coming, and I&amp;#39;m feeling happy that I made this change. &lt;/p&gt;
&lt;h2 id=&quot;i-dont-have-to-sell-myself-or-accessibility&quot;&gt;I Don&amp;#39;t Have to Sell Myself or Accessibility&lt;/h2&gt;
&lt;p&gt;One of the best things has been realizing that I don&amp;#39;t need to sell myself to do accessibility-related work anymore. It has been given due to my role. It might sound obvious, but it is not for me.&lt;/p&gt;
&lt;p&gt;You see, I&amp;#39;ve been in a position before where I&amp;#39;ve expressed that I want to do more accessibility-related work. I&amp;#39;ve been very vocal about that. In the end, I&amp;#39;ve still been the front-end dev first and foremost, who doesn&amp;#39;t have time to look into making the results of my work accessible. If accessibility didn&amp;#39;t take too much time, it was ok to think about, but there were never actual resources for that. &lt;/p&gt;
&lt;p&gt;Those situations happened when I was working in a consultancy. I was sold as a front-end developer, and that was what the customer was paying for. They didn&amp;#39;t have accessibility in their roadmaps, so they didn&amp;#39;t want to spend money on that. And myself being a consultant, well, I cost money. &lt;/p&gt;
&lt;p&gt;So I&amp;#39;ve been super happy that whenever I&amp;#39;m talking about changing things due to accessibility reasons, just saying that is enough. I don&amp;#39;t have to justify to people how they benefit from it. &lt;/p&gt;
&lt;p&gt;That means a lot. It has allowed me to use my skills and energy to actually do something without having to spend a long time selling the idea. &lt;/p&gt;
&lt;h2 id=&quot;im-expected-to-concentrate-on-accessibility&quot;&gt;I&amp;#39;m Expected to Concentrate on Accessibility&lt;/h2&gt;
&lt;p&gt;Continuing from the previous point, I&amp;#39;m super happy that I can concentrate on accessibility. It&amp;#39;s actually expected of me. I kind of knew that from day one, but after a couple of weeks, I had this moment when it really hit me. I&amp;#39;m not the front-end developer anymore, but the one who does accessibility. &lt;/p&gt;
&lt;p&gt;That also means that I don&amp;#39;t need to agree to complete something first, and then, if there is still time, I can look into accessibility-specific matters. No, I get to do it right away. That&amp;#39;s expected of me. &lt;/p&gt;
&lt;p&gt;One example is that right now, I&amp;#39;m doing an audit of one of our services. I even got my own epic to do that. And it feels good. Maybe you might think that hey, that&amp;#39;s nothing special. Well, for me, it is. Coming from a situation where I didn&amp;#39;t get to do what I wanted, this feels amazing.&lt;/p&gt;
&lt;h2 id=&quot;im-not-alone&quot;&gt;I&amp;#39;m not alone&lt;/h2&gt;
&lt;p&gt;Another thing that&amp;#39;s great at Oura is that I&amp;#39;m not alone in my accessibility mission. There are people across the company who understand the value of accessible services and that we need to do better. Yes, as mentioned, there&amp;#39;s still a lot to do, but I feel that I&amp;#39;m not alone in this. &lt;/p&gt;
&lt;p&gt;Heck, we even have this part in our values:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Humanity is present in all that we do—connecting with kindness and empathy. We build products and culture that celebrate and support uniqueness, inclusivity, and diversity, where everyone belongs. We meet people where they are, as they are, and &lt;strong&gt;prioritize accessibility.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;#39;ve had so many great conversations with people during the past weeks. And what has surprised me the most was that people were waiting for me to start. In multiple different conversations, I&amp;#39;ve heard that &amp;quot;Oh, I&amp;#39;ve been waiting for you to join! I have so many questions for you!&amp;quot; That feels just amazing.  &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So, as you might have guessed, I&amp;#39;m excited. There&amp;#39;s certainly so much to do, but I get to work with talented people who care about their users. I believe that in the future, we can really say that our data is accessible, if not for all, then at least for a wider range of people. &lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ouraring.com/about-us&quot;&gt;Oura - About Us&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Aria-Label is Not Always the Answer</title>
    <link href="https://eevis.codes/blog/2021-11-29/aria-label-is-not-always-the-answer/" />
    <updated>2023-01-03T08:57:46.482Z</updated>
    <id>https://eevis.codes/blog/2021-11-29/aria-label-is-not-always-the-answer/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1ZEK0v1IYY0biGSzSxEpX/001a7769952bf05d26617a587ae6a487/aria-label-is-not-always-the-answer.png"/>]]>
      &lt;p&gt;Okay, you&amp;#39;ve learned about accessibility and that there is sometimes a need to make, for example, the link text more descriptive for screen reader users. You&amp;#39;ve learned about the &lt;code&gt;aria-label&lt;/code&gt;-attribute, and its powers. It&amp;#39;s the answer to everything!&lt;/p&gt;
&lt;p&gt;I&amp;#39;m afraid I have to say that it is not. It doesn&amp;#39;t work with all HTML elements. In this blog post, I&amp;#39;ll dig a bit deeper into the &lt;code&gt;aria-label&lt;/code&gt;-attribute, and its usage and limitations, and options. &lt;/p&gt;
&lt;h2 id=&quot;what-is-aria-label&quot;&gt;What Is &lt;code&gt;aria-label&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;So, let&amp;#39;s first talk about the specification and where to use it. &lt;a href=&quot;https://www.w3.org/TR/wai-aria/&quot;&gt;W3C Recommendation for Accessible Rich Internet Applications (WAI-ARIA)-document&lt;/a&gt; defines &lt;code&gt;aria-label&lt;/code&gt; as &amp;quot;a string value that labels the current element.&amp;quot; You can use it to provide a recognizable name of the object if there is no visible text on the screen that could be used as the label. If there was, &lt;code&gt;aria-labelledby&lt;/code&gt; should be used.   &lt;/p&gt;
&lt;p&gt;The most important thing, and the reason I&amp;#39;m writing this article, is that developers should use it only with the following elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;interactive elements (such as links, buttons, inputs et cetera)&lt;/li&gt;
&lt;li&gt;Elements that have a landmark role&lt;/li&gt;
&lt;li&gt;Elements that have an ARIA-widget role&lt;/li&gt;
&lt;li&gt;&lt;code&gt;img&lt;/code&gt; or &lt;code&gt;iframe&lt;/code&gt;-elements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://www.tpgi.com/short-note-on-aria-label-aria-labelledby-and-aria-describedby/&quot;&gt;Short note about &lt;code&gt;aria-label,&lt;/code&gt; &lt;code&gt;aria-labelledby&lt;/code&gt; and &lt;code&gt;aria-describedby&lt;/code&gt; by Léonie Watson.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;aria-label&lt;/code&gt; on these elements works consistently with assistive technologies. The support is inconsistent if you use it with any other tag, such as &lt;code&gt;div&lt;/code&gt; or &lt;code&gt;span.&lt;/code&gt; More about that in &lt;a href=&quot;https://eevis.codes/blog/2021-11-29/aria-label-is-not-always-the-answer/#aria-label-and-non-supported-elements&quot;&gt;Aria-label and non-supported elements&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s look into each one of these elements a bit more.&lt;/p&gt;
&lt;h3 id=&quot;interactive-elements&quot;&gt;Interactive Elements&lt;/h3&gt;
&lt;p&gt;Interactive elements are tags intended for user interaction. It means, for example, &lt;code&gt;button&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt; (when &lt;code&gt;href&lt;/code&gt;-attribute is present), &lt;code&gt;input&lt;/code&gt; , &lt;code&gt;details&lt;/code&gt; and others. The main idea is that users can interact with them. &lt;/p&gt;
&lt;h3 id=&quot;elements-with-landmark-role&quot;&gt;Elements with Landmark Role&lt;/h3&gt;
&lt;p&gt;Elements can have landmark roles either implicitly or explicitly. Having an implicit role means that some of the HTML elements have roles set to them natively. The explicit role, on the other hand, is set with &lt;code&gt;role&lt;/code&gt;-attribute. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ll list the different roles and elements that already have those roles:&lt;/p&gt;
&lt;table&gt;
&lt;caption&gt;Landmark-roles with HTML-elements having those roles&lt;/caption&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;HTML-element&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;banner&lt;/td&gt;
&lt;td&gt;(document) &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;complementary&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;contentinfo&lt;/td&gt;
&lt;td&gt;(document) &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;form&lt;/td&gt;
&lt;td&gt;&amp;lt;form&amp;gt; (if provided an accessible name via &lt;code&gt;aria-label&lt;/code&gt; or &lt;code&gt;aria-labelledby&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;
&lt;td&gt;main&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;navigation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;region&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Source: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles#3._landmark_roles&quot;&gt;MDN&amp;#39;s list of landmark roles&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In addition to the roles listed, there&amp;#39;s a &lt;code&gt;search&lt;/code&gt;-role, which is only an explicit role, so, set with &lt;code&gt;role&lt;/code&gt;-attribute.&lt;/p&gt;
&lt;h3 id=&quot;elements-with-aria-widget-role&quot;&gt;Elements with ARIA-widget Role&lt;/h3&gt;
&lt;p&gt;ARIA-widget roles define typical interactive patterns. These patterns usually need JavaScript to implement their intended behavior.  There are multiple ARIA-widget roles, and I&amp;#39;m going to list a couple of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tab&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tablist&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;searchbox&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;treeitem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;switch&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find more from &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles#2._widget_roles&quot;&gt;MDN&amp;#39;s list of ARIA-widget roles&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;img-and-iframe&quot;&gt;&lt;code&gt;img&lt;/code&gt; and &lt;code&gt;iframe&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Image and iframe are two elements where &lt;code&gt;aria-label&lt;/code&gt; works as well. For &lt;code&gt;img,&lt;/code&gt; there are rare cases when you might need &lt;code&gt;aria-label.&lt;/code&gt; In general, you should use the &lt;code&gt;alt&lt;/code&gt;-attribute to provide an accessible name for the image. &lt;/p&gt;
&lt;h2 id=&quot;how-does-aria-label-work&quot;&gt;How does &lt;code&gt;aria-label&lt;/code&gt; work?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;aria-label&lt;/code&gt; overrides the visible text in elements, where the accessible name would come from the child element. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button aria-label=&amp;quot;Something else&amp;quot;&amp;gt;
  click me
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above button would be exposed as &amp;quot;Something else&amp;quot; to screen reader users.&lt;/p&gt;
&lt;h2 id=&quot;aria-label-and-non-supported-elements&quot;&gt;&lt;code&gt;aria-label&lt;/code&gt; and non-supported elements&lt;/h2&gt;
&lt;p&gt;So, as mentioned, the support for &lt;code&gt;aria-label&lt;/code&gt; is inconsistent with, for example, &lt;code&gt;div&lt;/code&gt; and &lt;code&gt;span.&lt;/code&gt; If the &lt;code&gt;div&lt;/code&gt; or &lt;code&gt;span&lt;/code&gt; doesn&amp;#39;t have any role, &lt;code&gt;aria-label&lt;/code&gt; is ignored. The same goes for other non-interactive elements, such as &lt;code&gt;p,&lt;/code&gt; &lt;code&gt;ul,&lt;/code&gt; &lt;code&gt;li,&lt;/code&gt; or &lt;code&gt;legend&lt;/code&gt; as well. &lt;/p&gt;
&lt;p&gt; If they have a landmark role, support is still inconsistent. Per &lt;a href=&quot;https://www.w3.org/TR/using-aria/#notes2&quot;&gt;Notes on ARIA Use in HTML&lt;/a&gt;, some widget roles work better than others:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Its fine to use aria-label or aria-labelledby on div elements with role=navigation, role=search, role=main, JAWS doesn&amp;#39;t support them on role=banner, role=complementary, role=contentinfo. NVDA, VoiceOver, and Talkback are OK.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;options-for-aria-label&quot;&gt;Options for &lt;code&gt;aria-label&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;If &lt;code&gt;aria-label&lt;/code&gt; is not the answer, then what is? Well, it depends. In general, I would recommend adding visible text. Usually, when you&amp;#39;re adding text for screen-reader users, it would be helpful for sighted users as well. &lt;/p&gt;
&lt;p&gt;However, if a non-visible text is needed, you could add visually hidden text with CSS. You can read more and find a CSS-snippet from The A11Y Project&amp;#39;s article &lt;a href=&quot;https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/&quot;&gt;Hide Content&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;aria-label&lt;/code&gt; is a helpful tool in cases when you need to add an accessible name for an element, and there is no already visible text to be used. However, you can&amp;#39;t use it with every HTML tag. In general, those tags that don&amp;#39;t work are non-interactive elements. &lt;/p&gt;
&lt;p&gt;There are exceptions: If, for example, &lt;code&gt;div&lt;/code&gt; has one of the roles listed above, then screen readers read it. But there are inconsistencies with some roles: for example, roles &lt;code&gt;banner&lt;/code&gt;, &lt;code&gt;complementary,&lt;/code&gt; and &lt;code&gt;contentinfo&lt;/code&gt; are something that JAWS ignores, while VoiceOver, TalkBack, and NVDA support them. &lt;/p&gt;
&lt;p&gt;I listed two options for cases when &lt;code&gt;aria-label&lt;/code&gt; doesn&amp;#39;t work. You could add a visible text, as it usually would be helpful for all users. Another option is to add visually hidden text. &lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/wai-aria/&quot;&gt;W3C Recommendation for Accessible Rich Internet Applications (WAI-ARIA)-document&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.tpgi.com/short-note-on-aria-label-aria-labelledby-and-aria-describedby/&quot;&gt;Short note about &lt;code&gt;aria-label,&lt;/code&gt; &lt;code&gt;aria-labelledby&lt;/code&gt; and &lt;code&gt;aria-describedby&lt;/code&gt; by Léonie Watson.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles#3._landmark_roles&quot;&gt;MDN&amp;#39;s list of landmark roles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles#2._widget_roles&quot;&gt;MDN&amp;#39;s list of ARIA-widget roles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/using-aria/#notes2&quot;&gt;Notes on ARIA Use in HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The A11y Project - &lt;a href=&quot;https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/&quot;&gt;Hide Content&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>How Not to Create a Button</title>
    <link href="https://eevis.codes/blog/2021-12-13/how-not-to-create-a-button/" />
    <updated>2023-01-03T08:57:45.129Z</updated>
    <id>https://eevis.codes/blog/2021-12-13/how-not-to-create-a-button/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1HIrnvly2xzuvf9051NE4A/77d55416ce30e12b32c19f4226d306cb/Twitter_post_-_5how-not-to-create-a-button__1_.png"/>]]>
      &lt;p&gt;I have a habit of testing websites with a keyboard. I often encounter buttons that don&amp;#39;t work as expected. It&amp;#39;s frustrating, and I usually start digging into the source code to understand why the button isn&amp;#39;t working. These reasons vary - some of them could be described as &amp;quot;normalized&amp;quot; patterns (I disagree), and some are really imaginative. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ll share some ways to &lt;strong&gt;not&lt;/strong&gt; create a button in this blog post. I&amp;#39;ll also explain why those patterns aren&amp;#39;t a good idea. You can find instructions on creating a button the right way in the last section, &lt;a href=&quot;https://eevis.codes/blog/2021-12-13/how-not-to-create-a-button/#the-right-way-to-create-a-button&quot;&gt;The Right Way to Create a Button&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;div-as-a-button&quot;&gt;Div as a Button&lt;/h2&gt;
&lt;p&gt;This &lt;code&gt;div&lt;/code&gt; as a button pattern is by far the most common. Here&amp;#39;s a code snippet to demonstrate it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div onClick=&amp;quot;...&amp;quot;&amp;gt;
  I pretend to be a button
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes these elements even have &lt;code&gt;tabindex=&amp;quot;0&amp;quot;&lt;/code&gt; to make them focusable with a keyboard. But they never, ever seem to have the keyboard event listeners for &lt;kbd&gt;space&lt;/kbd&gt; and &lt;kbd&gt;enter&lt;/kbd&gt;.  This means that even if the user can focus on the so-called button, they can&amp;#39;t activate it with a keyboard.&lt;/p&gt;
&lt;p&gt;Another thing that is often missing is the role attribute to communicate that there is a button. When it&amp;#39;s missing, non-sighted screen reader users won&amp;#39;t even know any button exists. For them, that particular element is a focusable text they might encounter if they&amp;#39;re browsing the page&amp;#39;s content.&lt;/p&gt;
&lt;p&gt;So, while this pattern works for mouse users, it&amp;#39;s excluding most of the other user groups out there. It&amp;#39;s possible to make a &lt;code&gt;div&lt;/code&gt; work as a button for all these groups, but it&amp;#39;s lots of work and makes the code harder to maintain. And you know, by using the button element, you&amp;#39;d get all that for free.&lt;/p&gt;
&lt;h2 id=&quot;link-as-a-button&quot;&gt;Link as a Button&lt;/h2&gt;
&lt;p&gt;Another HTML element I often see used as a button is the anchor-element, so, link. In these cases, it&amp;#39;s styled to look like a button. However, underneath it all, it&amp;#39;s a link:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a href=&amp;quot;...&amp;quot;&amp;gt;
  I, too, pretend to be a button
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;quot;But call to actions are a thing, right?&amp;quot; you might ask. Yeah, I know, they&amp;#39;re a pattern that is widely used. And it&amp;#39;s a bit confusing pattern - they often look like a button but then lead to another part of the website. And they don&amp;#39;t initiate an action, such as opening a modal.&lt;/p&gt;
&lt;p&gt;From the usage point of view, it doesn&amp;#39;t make much difference for a sighted mouse user, whether it&amp;#39;s a link or button under the hood. I mean, it works when clicking, be it a button or a link. &lt;/p&gt;
&lt;p&gt;However, for non-mouse users, it does. For example, keyboard users have certain ways to interact with an element. They can activate a button with &lt;kbd&gt;space&lt;/kbd&gt; and &lt;kbd&gt;enter&lt;/kbd&gt;, and link only with &lt;kbd&gt;enter&lt;/kbd&gt;. This means, that the so-called button is not actionable with &lt;kbd&gt;space&lt;/kbd&gt;.&lt;/p&gt;
&lt;p&gt;And why it is a problem? Well, have you ever pressed &lt;kbd&gt;space&lt;/kbd&gt; on a site when it&amp;#39;s not focused anywhere? It scrolls the page down. And that&amp;#39;s what happens when you&amp;#39;re focused on a link and press &lt;kbd&gt;space&lt;/kbd&gt;. It&amp;#39;s frustrating as user needs to scroll back up where they were. It might be even painful for some users in cases where every extra keystroke causes pain. &lt;/p&gt;
&lt;p&gt;And then there&amp;#39;s this: When users don&amp;#39;t know that there is a link underneath the button, they don&amp;#39;t have the control to use it as they would with a link. This means that they might, for example, want to open the link to a new tab or window. &lt;/p&gt;
&lt;h2 id=&quot;link-with-a-role-of-button&quot;&gt;Link with a Role of Button&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a role=&amp;quot;button&amp;quot; tabindex=&amp;quot;0&amp;quot; onClick={…}&amp;gt;
  I&amp;#39;m a link, I&amp;#39;m a button. Who am I?
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one is my new favorite. I mean, it&amp;#39;s a creative way of making a button even more confusing for many. For screen reader users, it indeed seems like a button. It even works if they try to activate it with screen reader commands, as they trigger the click-handler. However, this type of button doesn&amp;#39;t work with keyboard navigation.&lt;/p&gt;
&lt;p&gt;Why, you ask? Isn&amp;#39;t it a link, so it works with &lt;kbd&gt;enter&lt;/kbd&gt;, right? The answer is no. That element is not a link because it no longer has the &lt;code&gt;href&lt;/code&gt;-attribute. Removing the &lt;code&gt;href&lt;/code&gt;-attribute strips away the semantics and keyboard interaction. This way, even the enter-key doesn&amp;#39;t work with the element. &lt;/p&gt;
&lt;p&gt;I think the most common reason for using this solution is libraries and frameworks hiding away the semantics of an element. My guess for this particular case is that there is some kind of CSS-in-JS-library in use, and the developer has needed the link&amp;#39;s styles for a button. Then they have extended the link component and added all the attributes.&lt;/p&gt;
&lt;h2 id=&quot;button-element-wrapping-a-link&quot;&gt;Button Element Wrapping a Link&lt;/h2&gt;
&lt;p&gt;Okay, the previous two examples are relatively common ones. The following two are (hopefully) not that popular. &lt;/p&gt;
&lt;p&gt;First, I present to you a button element wrapping a link:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button&amp;gt;
  &amp;lt;a href=&amp;quot;...&amp;quot;&amp;gt;
    I&amp;#39;m a link wrapped in a button. And I don&amp;#39;t really work
  &amp;lt;/a&amp;gt;
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, let&amp;#39;s start with the fact that this is incorrect HTML. A link can&amp;#39;t be inside a button, as the HTML specification defines the button&amp;#39;s content model:&lt;/p&gt;
&lt;figure&gt;
    &lt;blockquote&gt;
        &lt;p&gt;Phrasing content, but there must be no interactive content descendant and no descendant with the tabindex attribute specified.&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;figcaption&gt;&lt;cite&gt;&lt;a href=&quot;https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element&quot;&gt;The button-element&lt;/a&gt;&lt;/cite&gt; in HTML-specification&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Even though anchor-tag is phrasing content, it&amp;#39;s also interactive, so this rules it out. &lt;/p&gt;
&lt;p&gt;This pattern also does not work well for screen reader or keyboard users. Because the link is wrapped with a button, there&amp;#39;s an extra tab stop before the user reaches the actual link. And as the button doesn&amp;#39;t have a click-handler, trying to activate it doesn&amp;#39;t do anything. So, the user feels like they&amp;#39;re on a control, but it doesn&amp;#39;t work, as the button still gets focused.&lt;/p&gt;
&lt;p&gt;That is really frustrating and confusing for the user. If they try to go forward and tab to the link element and then press &lt;kbd&gt;enter&lt;/kbd&gt;, they&amp;#39;ll get the &amp;quot;button&amp;quot; to work. However, it is not clear that there is a next tab stop. And with the link, all the same problems presented in the previous section remain. &lt;/p&gt;
&lt;p&gt;For screen reader users, it presents even more problems. I tested this pattern with VoiceOver and couldn&amp;#39;t get it to work at all. I&amp;#39;m not sure about the other screen readers, but I&amp;#39;d guess they also have problems.&lt;/p&gt;
&lt;h2 id=&quot;checkbox-label-as-a-button&quot;&gt;Checkbox Label as a Button&lt;/h2&gt;
&lt;p&gt;This pattern has been the wildest of them all. And at the same time, I understand how a developer has ended up with this pattern. So, in this case, we have a checkbox-input, which is visually hidden, and then the label is styled to look like a button. The code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;label htmlFor=&amp;quot;checkbox&amp;quot;&amp;gt;
  I also want to be a button
&amp;lt;/label&amp;gt;
&amp;lt;input 
  id=&amp;quot;checkbox&amp;quot; 
  type=&amp;quot;checkbox&amp;quot; 
  checked /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As mentioned, I understand how the developer ended up with this. There&amp;#39;s this idea of changing something based on two states. The data that this particular checkbox represents can either be on or off. And based on that state, there are changes in the UI. &lt;/p&gt;
&lt;p&gt;However, the checkbox serves another role in the UI. As Heydon Pickering mentions in &lt;a href=&quot;http://book.inclusive-components.design/&quot;&gt;Inclusive Components&lt;/a&gt;, users might suspect that they&amp;#39;re also choosing a value for submission. This, however, is true only for screen reader users, as they hear that the underlying component is a checkbox. &lt;/p&gt;
&lt;p&gt;The problem for sighted users is that as the label is styled to look like a button, they expect it&amp;#39;s a button. But if they try to interact with it using a keyboard, it gets confusing. You see, the key that toggles a checkbox is &lt;kbd&gt;space&lt;/kbd&gt;. So it doesn&amp;#39;t work with &lt;kbd&gt;enter&lt;/kbd&gt; at all. &lt;/p&gt;
&lt;p&gt;So, after all these not-so-great ways of creating a button, let&amp;#39;s look into making it the right way.&lt;/p&gt;
&lt;h2 id=&quot;the-right-way-to-create-a-button&quot;&gt;The Right Way to Create a Button&lt;/h2&gt;
&lt;p&gt;It&amp;#39;s pretty simple. Use the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;-element. It has everything out of the box: the role of a button to convey its semantics, tab index to put it into tab order, and easiness of giving it a click handler that handles the keyboard interaction as well. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button onClick={...}&amp;gt;
  I am actual button
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This helps your and your colleagues&amp;#39; lives, as the code is easier to maintain. Also, using semantic elements helps the users - not everyone uses a mouse, so having the expected keyboard interaction is required. You don&amp;#39;t want to exclude anyone, right?&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element&quot;&gt;The button-element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://book.inclusive-components.design/&quot;&gt;Inclusive Components&lt;/a&gt; by Heydon Pickering&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Year in Review - 2021 Edition</title>
    <link href="https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/" />
    <updated>2023-01-03T08:57:44.768Z</updated>
    <id>https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1ieXXmfajNXZeX1ACIb8Zu/a7cda345190c264a98b2b28747166d5f/Twitter_post_-_6year-in-review__1_.png"/>]]>
      &lt;p&gt;It&amp;#39;s that time of the year again: time to look back and summarize what has happened last year. And when I started listing different things, oh boy, what a year has it been! &lt;/p&gt;
&lt;p&gt;This blog post will look into some of the highs and lows of 2021 in my life. I also checked what I&amp;#39;ve added to Polywork and made a collection of 2021 highlights. There are some additional things there, so if you want to have a look, here&amp;#39;s the link: &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.polywork.com/eevis/collections/868636&quot;&gt;2021 Highlights in Polywork&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s dive in.&lt;/p&gt;
&lt;h2 id=&quot;blogging&quot;&gt;Blogging&lt;/h2&gt;
&lt;p&gt;So, I started blogging a little over a year ago. First, I was publishing my posts in Dev.to. In the fall, I finally put together my own blog, and since then, I&amp;#39;ve published the posts first there and then cross-posted to Dev.to. &lt;/p&gt;
&lt;p&gt;In addition to that, I&amp;#39;ve published a couple of other blog posts, one in Mimmit Koodaa (a Finnish organization working towards equality in tech) and in my previous workplace&amp;#39;s blog. &lt;/p&gt;
&lt;p&gt;I want to mention a couple of my favorites:&lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Don&#39;t Develop Just for Yourself - A Developer&#39;s Checklist to Accessibility&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2021-05-28&quot;&gt;May 28&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2021-05-28/dont-develop-just-for-yourself-a-developers-checklist-to-accessibility&quot;&gt;Read the post Don&#39;t Develop Just for Yourself - A Developer&#39;s Checklist to Accessibility&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;This blog post has been part of my talk with the same name, and I&amp;#39;ve shared it when speaking at conferences. I&amp;#39;ve also been able to use it at work; I&amp;#39;ve been constructing checklists for PR templates, and this blog post has been really helpful. &lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Why I&#39;m Not One of the Guys&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2021-07-24&quot;&gt;Jul 24&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2021-07-24/why-im-not-one-of-the-guys&quot;&gt;Read the post Why I&#39;m Not One of the Guys&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;This blog post has been one of the hardest to write and publish. When I cross-posted it to Dev, I got tons of comments - many telling me how wrong I am to feel excluded. Some of them were really rude, and I noticed I&amp;#39;m still reluctant to publish posts in Dev after that. &lt;/p&gt;
&lt;p&gt;In addition to rude comments, I got supportive comments, and I am super happy about them. So thank you all who wrote them!&lt;/p&gt;
&lt;p&gt;I guess one of the reasons I feel so bad after the comments is that this was the first time I faced this much hatred towards my opinions on the internet. And I&amp;#39;m sad to see that in the developer community, there are so many people who want to ignore the feelings of those in minorities.&lt;/p&gt;
&lt;p&gt;Although this was the hardest to write and publish, I&amp;#39;m proud that I did it. &lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;5 Things I&#39;ve Learned as a Female Developer&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2021-01-24&quot;&gt;Jan 24&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://dev.to/eevajonnapanula/5-things-i-ve-learned-as-a-female-developer-p19&quot;&gt;Read the post 5 Things I&#39;ve Learned as a Female Developer&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;The points I list in this blog post are something I&amp;#39;ve come back to throughout the year. I&amp;#39;ve been either reminding myself about them or the others in different instances. The most important point has been that nobody knows everything. And yet, we often feel bad and talk ourselves down because we think we should know everything. &lt;/p&gt;
&lt;p&gt;Oh, and one more accomplishment in blogging: I&amp;#39;ve started recording my blog posts. That means they will be available in both written and audio formats and thus serve more people. &lt;/p&gt;
&lt;h2 id=&quot;speaking&quot;&gt;Speaking&lt;/h2&gt;
&lt;p&gt;Last year I was also speaking a lot compared to the previous year. There was a total of 11 different events, plus some internal talks. Of these 11, seven were conferences, one was a vodcast, and the rest were meetups or other speaking engagements. &lt;/p&gt;
&lt;p&gt;The talk for the year was definitely &amp;quot;Don&amp;#39;t Develop Just for Yourself - A Developer&amp;#39;s Checklist to Accessibility.&amp;quot; I gave it in six events. I also talked about the theme of &amp;quot;Reduced motion&amp;quot; and equality in tech. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ll list three of my favorite events and say a couple of words about them. &lt;/p&gt;
&lt;h3 id=&quot;euruko-2021&quot;&gt;Euruko 2021&lt;/h3&gt;
&lt;p&gt;Euruko was so much fun! It was organized remotely, and they streamed some things from an actual studio. Most of the speakers gave their talks remotely, but I got to go to the studio to speak. &lt;/p&gt;
&lt;p&gt;Even though we had some minor technical difficulties, the experience was incredible. I mean, I got to meet some real people  Also the production was good.&lt;/p&gt;
&lt;p&gt;However, the community was the key to making the event fantastic. Even though I don&amp;#39;t use Ruby or Rails, I was already talking with someone that  I was already telling people that I will mark Euruko 2022 in my calendar right away because of the community. So, see you then!&lt;/p&gt;
&lt;h3 id=&quot;mimmit-koodaa-this-is-not-a-webinar&quot;&gt;Mimmit Koodaa This is Not a Webinar&lt;/h3&gt;
&lt;p&gt;Okay, this is something I just need to mention. It was the event where I received the Mimmit Koodaa-award (more on that later). But that was not all - I also gave a talk at the event with my (former) colleague. &lt;/p&gt;
&lt;p&gt;We spoke about the importance of role models and representation and how to encourage representatives of minorities to be those role models. &lt;/p&gt;
&lt;p&gt; It was my first talk where I collaborated with somebody. As the Covid-situation did not let us practice in person (and the talk was streamed live from a studio, so we were at the same place), I was super nervous. But it went well! &lt;/p&gt;
&lt;p&gt;And it actually led to an opportunity to be a guest in a radio program for the Finnish Broadcasting Company (YLE). So, I also made my first visit to the radio in 2021.&lt;/p&gt;
&lt;h3 id=&quot;inclusive-design-24&quot;&gt;Inclusive Design 24&lt;/h3&gt;
&lt;p&gt;Speaking at Inclusive Design 24 was definitely one of the year&amp;#39;s highlights. Even though I&amp;#39;ve decided my strategy in speaking about accessibility is to get to the conferences that are not about accessibility, ID24 was something I have dreamed of for some time. And when the email came that they accepted my talk, I was thrilled. &lt;/p&gt;
&lt;p&gt;I spoke about reduced motion, a theme dear to me. The response was great, and I also got to meet one of my role models in the accessibility world, Sarah Higley, as she was hosting the session.&lt;/p&gt;
&lt;p&gt;Also, the other talks were so good. I&amp;#39;ve been watching the recordings and got some ideas from them. For example, the &lt;a href=&quot;https://www.youtube.com/watch?v=aGl38gB37zA&amp;amp;list=PLn7dsvRdQEfFoUIFxtSsp8PjHm-glki1Z&quot;&gt;talk about Auditorial&lt;/a&gt; gave me the idea to start recording my blog posts and adding a possibility to listen to them. &lt;/p&gt;
&lt;h2 id=&quot;accomplishments&quot;&gt;Accomplishments&lt;/h2&gt;
&lt;p&gt;When looking back last year, it&amp;#39;s been a year of accomplishments. I&amp;#39;ll name a few.&lt;/p&gt;
&lt;h3 id=&quot;mimmit-koodaa-award&quot;&gt;Mimmit Koodaa-award&lt;/h3&gt;
&lt;p&gt;First, &lt;strong&gt;Mimmit Koodaa-award&lt;/strong&gt;. I was the first person in history to receive the award for my volunteering and my work for equality and breaking the IT industry&amp;#39;s stereotypes. It&amp;#39;s a great honor. One fantastic thing regarding the award was that my role model, former president of Finland Tarja Halonen, presented it. She has done so much for equality and has had a remarkable career. I highly recommend reading about her, if you haven&amp;#39;t yet. &lt;/p&gt;
&lt;h3 id=&quot;finalist-in-nordic-women-in-tech-awards&quot;&gt;Finalist in Nordic Women in Tech Awards&lt;/h3&gt;
&lt;p&gt;Continuing with the awards theme, I was nominated and shortlisted to be a finalist in the Nordic Women in Tech Awards in the category Rising star of the year. That was an honor!&lt;/p&gt;
&lt;h3 id=&quot;naisten-linja&quot;&gt;Naisten Linja&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://naistenlinja.fi/&quot;&gt;Naisten Linja&lt;/a&gt; works to help women and girls who have faced violence. I&amp;#39;ve been working to help build their website as a volunteer. That has been one of the most meaningful things for the last year. The work they do is super important.&lt;/p&gt;
&lt;h3 id=&quot;started-at-oura&quot;&gt;Started at Oura&lt;/h3&gt;
&lt;p&gt;And finally, the one thing I&amp;#39;m super happy about is that  I switched jobs from the consulting world to a product company to work as an accessibility specialist. If you want to read my initial thoughts about the first month, head to this blog post I wrote: &lt;a href=&quot;https://eevis.codes/blog/2021-11-22/first-month-as-an-accessibility-specialist-at-oura/&quot;&gt;First Month as an Accessibility Specialist at Oura&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;things-you-probably-didnt-see&quot;&gt;Things You Probably Didn&amp;#39;t See&lt;/h2&gt;
&lt;h3 id=&quot;i-got-tired&quot;&gt;I Got Tired&lt;/h3&gt;
&lt;p&gt;It was a challenging year. Not from the remote working perspective, no. That is actually something I enjoy. But there have been some other contributors that have led me to be on sickness leave. &lt;/p&gt;
&lt;p&gt;It has been primarily work-related, which was one reason I switched jobs. Right now, I&amp;#39;m happy to be working in a role that I have control over, and I get to concentrate on accessibility.&lt;/p&gt;
&lt;p&gt;Last spring was hard, and there were so many changes and uncertainties in the project I was working on. And this was even though the project team was fantastic! Many things worked smoothly, and we had good communication with the client. We had control over many things related to the project we were doing. People on the team - both from our and the client&amp;#39;s side - were nice. &lt;/p&gt;
&lt;p&gt;However, I was tired when the summer was approaching because of uncertainties and other things. To be honest, I was exhausted. And I realized that even though we had control over the project-related things, the uncertainties came from the corporation level, and no one in our team could do anything about it.&lt;/p&gt;
&lt;p&gt;Maybe the best decision for the whole spring was to call the occupational health doctor and get sickness leave for the last two weeks before the holidays. &lt;/p&gt;
&lt;p&gt;When the worst was over and I started to see more clearly, I realized that I needed change. Many of the problems that caused my condition were because I was working in a consultancy. So I sent an application to the one company I had often said that I would apply to when I wanted to switch away from consulting. I could say that the rest is history. Or some other cliché. &lt;/p&gt;
&lt;h3 id=&quot;exploring-the-nature&quot;&gt;Exploring the Nature&lt;/h3&gt;
&lt;p&gt;I am one of those who picked up a new outdoor hobby in 2020. I learned kayaking in a local club, and I could say that was one of the best decisions ever. I&amp;#39;ve seen so many incredible,  beautiful places that you can only reach by water.. And I&amp;#39;ve continued learning this year, and my next goal is to pass Euro Padel Pass level 2. &lt;/p&gt;
&lt;p&gt;Another thing I&amp;#39;ve picked up in the last two years has been hiking. I&amp;#39;ve visited some Finnish national parks and can only repeat that Finnish nature is stunning. And nights in a hammock, away from all the rush and negativity on the internet, that is just amazing.&lt;/p&gt;
&lt;p&gt;To give some numbers from this theme, I visited 11 out of 41 Finnish national parks last year. I plan to visit all of them at some point, but as I live in the south and many are in Lapland, it will take some time to accomplish.&lt;/p&gt;
&lt;h2 id=&quot;so-whats-next&quot;&gt;So, What&amp;#39;s Next?&lt;/h2&gt;
&lt;p&gt;I don&amp;#39;t want to set anything in stone. These past years have shown that anything can change in an instant, and so can my plans. However, there are some themes I&amp;#39;d love to see being part of my life this year. &lt;/p&gt;
&lt;h3 id=&quot;cognitive-accessibility&quot;&gt;Cognitive Accessibility&lt;/h3&gt;
&lt;p&gt;This year, I want to learn more about cognitive accessibility. I think I have a basic knowledge about it, but I want to go deeper. &lt;/p&gt;
&lt;p&gt;This goal is related to so many things - work, studies (technical communications), and personal life. I want to understand it better and to be able to help people around me to create content that is more accessible for everyone, and especially for those who need cognitive accessibility.&lt;/p&gt;
&lt;h3 id=&quot;accessibility-content-in-finnish&quot;&gt;Accessibility Content in Finnish&lt;/h3&gt;
&lt;p&gt;I&amp;#39;ve noticed that writing (and speaking) about accessibility is more natural for me in English. My approach to content creation around accessibility has been about the content I needed - and one thing I want to improve is that I create content in Finnish as well. That&amp;#39;s something I totally would have needed when I started learning about coding and accessibility.&lt;/p&gt;
&lt;p&gt;So, I have some ideas for content creation in Finnish in 2022. However, I will not stress myself out about it.&lt;/p&gt;
&lt;h3 id=&quot;spend-more-time-outdoors&quot;&gt;Spend More Time Outdoors&lt;/h3&gt;
&lt;p&gt;As mentioned in the previous section, I&amp;#39;ve picked up some covid-safe hobbies such as kayaking and hiking. I want to continue with those and spend some more time outdoors in 2022. I also want to spend more nights outside in my hammock, preferably after a day of kayaking in different places. And when there is no water around, hiking is okay too.&lt;/p&gt;
&lt;p&gt;So, to conclude this post, I want to wish you a happy New Year, may it be better than the previous ones!&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>How I Created Neule.art</title>
    <link href="https://eevis.codes/blog/2022-06-30/how-i-created-neule-art/" />
    <updated>2023-01-03T08:57:31.343Z</updated>
    <id>https://eevis.codes/blog/2022-06-30/how-i-created-neule-art/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2pF0Gd0FMopIJXzIaFK8wS/5798380f415b6e57e3750b155931010d/Twitter_post_-_8solving-non-coding-problems__2_.png"/>]]>
      &lt;p&gt;I&amp;#39;m a knitter. It&amp;#39;s one way of expressing my creativity and also passing the time. And it&amp;#39;s sometimes super relaxing. And I (and people around me) get to wear some nice, warm, self-made garments. &lt;/p&gt;
&lt;p&gt;One ongoing project is that I&amp;#39;m knitting some &lt;a href=&quot;https://lopidesign.is/en/shop/adults/sweaters/riddari/&quot;&gt;Riddari-sweaters&lt;/a&gt; as presents to some of my close ones when they turn 30. Usually, I&amp;#39;ve just decided on the colors myself, but this time I wanted to ask from the recipient for their color choice. &lt;/p&gt;
&lt;p&gt;So, I could have tried to find some pictures of the sweater in potential colors. But there was a chance that I couldn&amp;#39;t find those. And it takes some imagination to try to think how some colors play together if you only see a picture of the yarn ball and the sweater in random color. Trust me; I&amp;#39;ve bought some new yarn in the middle of making a sweater because what I was knitting was not what I had imagined.&lt;/p&gt;
&lt;p&gt;I needed a way of visualizing the colors somehow. And thus, the idea for my next project was born. In this blog post, I&amp;#39;ll introduce you to &lt;a href=&quot;https://neule.art/&quot;&gt;Neule.art&lt;/a&gt;, a color picker or color visualizer for the Riddari sweater (among other patterns), and how I created it. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/gOFqJwKAeZhBlxmllZJIG/7882dedaedebfab0cb27d1132714e66a/Screenshot_2024-11-03_at_13.58.08.png&quot; alt=&quot;A part of website, with a text &amp;quot;Pick your colors for a sweater&amp;quot; as the heading, and a drawn picture of an Icelandic sweater (Riddari-pattern) with colors black, lagoon, apricot and heaven blue. Next to the shirt there is a form with selects for four colors, and colors mentioned are selected.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re interested in the code, you can find it from the &lt;a href=&quot;https://github.com/eevajonnapanula/neule.art/&quot;&gt;Neule.art repository&lt;/a&gt;. The code is nowhere perfect, and I know I could polish it a lot, but my goal has been just to get it out and then, maybe at some point, improve the code quality.&lt;/p&gt;
&lt;h2 id=&quot;the-planning-and-decisions&quot;&gt;The Planning and Decisions&lt;/h2&gt;
&lt;p&gt;So, I began pondering how to create this site or app. I instantly started thinking about using SVGs for the visualization, as you can manipulate the colors of an SVG pretty straightforwardly. &lt;/p&gt;
&lt;p&gt;Another thing to decide on was the technologies. What should I use? React? NextJS? Something else? I wanted to build a page with the least possible amount of JavaScript, so I decided to go with Eleventy. That&amp;#39;s a framework I&amp;#39;ve used before, and my website, for example, is built on Eleventy.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m a bit bored with JavaScript and wanted to try if I could build the site without any client-side JS. As Eleventy is a static site generator, this is possible. Even though I use JS for development, the result can be without JavaScript - if I want it that way. But how can the site manage changing colors? Eleventy Serverless and HTML forms to the rescue.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s talk next about how I implemented the different components mentioned above.&lt;/p&gt;
&lt;h2 id=&quot;getting-the-svg&quot;&gt;Getting the SVG&lt;/h2&gt;
&lt;p&gt;SVG was a great idea, but there was a problem: there were no SVGs for the Riddari-sweater I knew of. First, I thought about drawing the sweater. It sounded like a great plan - until I remembered that I&amp;#39;m not much of an artist. So no drawing.&lt;/p&gt;
&lt;p&gt;After spending some time on the internet, I came across image tracing. That sounded like a plan, and after finally purchasing Procreate on my iPad and playing around a bit, I started tracing the shirt. It looked awesome.&lt;/p&gt;
&lt;p&gt;It was just that there was no SVG export from the Procreate app. What do? Well, the thing I do best: search for answers. After some time, I had installed Inkscape, and after trial and error, I finally had the shirt in SVG, where it was possible to manipulate colors by CSS.&lt;/p&gt;
&lt;h2 id=&quot;building-with-eleventy&quot;&gt;Building with Eleventy&lt;/h2&gt;
&lt;p&gt;Building the site could have been a tricky part. Fortunately, I had experience with Eleventy, specifically with serverless functions and Eleventy. &lt;/p&gt;
&lt;p&gt;I also wanted to use HTML to its total capacity -  in this case, it means using forms and form actions. I also didn&amp;#39;t want to use JS on the site unless it was &lt;em&gt;absolutely&lt;/em&gt; necessary. And spoiler alert: I didn&amp;#39;t have any client-side JS in the first version of the site. Okay, I use Eleventy (which is, indeed, a JavaScript library) for building the site, but everything works with HTML in the production site. &lt;/p&gt;
&lt;p&gt;At the time of writing, I&amp;#39;m building some progressive enhancements to change the colors dynamically without reloading the page. However, I will still keep in mind those who don&amp;#39;t want to or can&amp;#39;t have JavaScript enabled in the browser.&lt;/p&gt;
&lt;p&gt;But back to the process. I started building the site. &lt;/p&gt;
&lt;p&gt;First, I created a static site, showing the SVG of the shirt with default values. Then I added the Eleventy Serverless plugin and dynamic path to the site displaying the SVG. This way, I could pass the four colors for the shirt as a query parameter. &lt;/p&gt;
&lt;h3 id=&quot;using-the-native-html-form&quot;&gt;Using the Native HTML Form&lt;/h3&gt;
&lt;p&gt;Once I had done that, adding the form was the next step. If you&amp;#39;re unfamiliar with HTML native forms, they work so that when you hit &amp;quot;send&amp;quot; (or whatever the primary action is), they send the values from the form as an object to the URL you&amp;#39;ve defined in the &lt;code&gt;action&lt;/code&gt;-attribute. And if you use the &amp;quot;get&amp;quot;-method, you get the values as query parameters - which is perfect for what I was doing.&lt;/p&gt;
&lt;p&gt; So, here&amp;#39;s a code snippet I&amp;#39;m using in the project (I edited it and stripped away all Nunjucks-syntax I use for passing the data for clarity):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;form method=&amp;quot;get&amp;quot; action=&amp;quot;/en/colors/&amp;quot; class=&amp;quot;colors-form&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;color-a&amp;quot;&amp;gt;A (Main color):&amp;lt;/label&amp;gt;
  &amp;lt;select id=&amp;quot;color-a&amp;quot; name=&amp;quot;a&amp;quot;&amp;gt;
    &amp;lt;option value=&amp;quot;0059&amp;quot; selected&amp;gt;Black (0059)&amp;lt;/option&amp;gt;
    &amp;lt;option value=&amp;quot;9423&amp;quot;&amp;gt;Lagoon (9423)&amp;lt;/option&amp;gt;
    &amp;lt;!-- More color options --&amp;gt;
  &amp;lt;/select&amp;gt;
 &amp;lt;!-- More color selects --&amp;gt;
  &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;Check the result&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;getting-the-data-for-yarn-availability&quot;&gt;Getting the Data For Yarn Availability&lt;/h3&gt;
&lt;p&gt;Another thing I wanted to add to the site was a possibility to see if some selected stores had specific colorways available of the Lèttlopi-yarn (which knitters often use for Icelandic sweaters). And by &amp;quot;selected,&amp;quot; I mean stores I either knew have Léttlopi on stock or were easily found on Google. &lt;/p&gt;
&lt;p&gt;As I had this approach of not using client-side JavaScript, I needed to store the data in some way Eleventy could utilize it. Also, I didn&amp;#39;t want to scrape the sites every time users visited my page. After some consideration, I wrote a function that runs once a day at night and scrapes the available colors from the selected yarn stores&amp;#39; sites. &lt;/p&gt;
&lt;p&gt;I use Github actions and cron job for running the function. It scrapes through the yarn pages in the stores, parses the data to JSON,  and then saves the new values into a data file. Then the site gets rebuilt, using the updated data for yarn availability.&lt;/p&gt;
&lt;p&gt;Writing the scraper was fun. I used &lt;a href=&quot;https://cheerio.js.org/&quot;&gt;cheerio&lt;/a&gt; to get and find the relevant data from the document on the yarn stores&amp;#39; websites. Then I parsed it with JavaScript to JSON. The fun in this was that every site has its way of annotating available yarn. Every store was a new puzzle to solve so that I get the relevant piece of information - which colors of the yarn were available and which were not. &lt;/p&gt;
&lt;p&gt;If you go and check the &lt;a href=&quot;https://github.com/eevajonnapanula/neule.art/blob/main/helpers/getColors.js&quot;&gt;getColors.js&lt;/a&gt;-file, you can see that I&amp;#39;ve been using different techniques for different stores. Sometimes it used an id, sometimes Regex, sometimes splitting the string from specific places, and sometimes looking for a class name. &lt;/p&gt;
&lt;p&gt;When I had the yarn data available, I just needed to use it. And then, I had all the pieces together and had the MVP (minimum viable product) ready for publishing. &lt;/p&gt;
&lt;h2 id=&quot;publishing&quot;&gt;Publishing&lt;/h2&gt;
&lt;p&gt;As I had decided to use serverless functions, and as Eleventy has instructions only for Netlify, it was pretty straightforward to use Netlify to host the site. Also, Netlify itself is relatively straightforward, so setting it up was fast - and the fact that I&amp;#39;ve used Netlify for many things in the past helped in that.&lt;/p&gt;
&lt;p&gt;But the most challenging part of publishing the site was to buy the domain - or, rather, decide the domain name for the site. After pondering (and reading the list of possible top-level domains) for some time, it hit me. &amp;quot;Neule.art!&amp;quot; It&amp;#39;s perfect. &amp;quot;Neule&amp;quot; means knit garment in Finnish, and hey, knitting is art. Also, from the beginning, I had plans to add other patterns than Riddari to the site, so I didn&amp;#39;t want to use Riddari for the domain name. &lt;/p&gt;
&lt;p&gt;I bought the domain, spent some time figuring out all the DNS stuff, and finally, the site was live! I was so happy. You know, it&amp;#39;s not always obvious that one gets their side project published. I&amp;#39;ve started so many projects I&amp;#39;ve never finished, so it feels good to complete something finally. And that it is something I feel proud of and can share. &lt;/p&gt;
&lt;h2 id=&quot;sharing-the-site&quot;&gt;Sharing the Site&lt;/h2&gt;
&lt;p&gt;I shared the site with some of my friends, and their response was encouraging. So, I decided to share the project on LinkedIn. Suddenly, it got so many comments and likes, and someone shared it on Facebook&amp;#39;s Icelandic sweater/yarn-related groups. On the first day, I got a couple of thousands of visits (it&amp;#39;s a lot for me and a niche page).&lt;/p&gt;
&lt;p&gt;What was super encouraging were the comments and feedback people shared with me. I was solving a problem for myself - and solved it for many others at the same time. It feels great to be able to help.&lt;/p&gt;
&lt;h2 id=&quot;what-ive-added-after-the-launch&quot;&gt;What I&amp;#39;ve Added After the Launch&lt;/h2&gt;
&lt;p&gt;I launched the site in mid-May. I&amp;#39;ve been working to improve it since then. I&amp;#39;ve added a possibility to generate random colors, added a version of the Riddari-sweater where users can change color for every motif (instead of the original pattern&amp;#39;s four colors), and added a new yarn store (Lanka-Kaisa). &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve also done a lot of under-the-hood fixes and features, such as adding cypress tests, fixing bugs, and improving the SEO of the website. &lt;/p&gt;
&lt;h2 id=&quot;the-future&quot;&gt;The Future&lt;/h2&gt;
&lt;p&gt;I have plans to add more patterns and yarns to the site. Also, as mentioned, I&amp;#39;m working on improving the user experience by providing the possibility to change colors dynamically. &lt;/p&gt;
&lt;p&gt;Also, I&amp;#39;m happy to hear ideas and feature requests to the site - you can either contact me (&lt;a href=&quot;https://eevis.codes/blog/2022-06-30/how-i-created-neule-art/hello@eevis.codes&quot;&gt;hello@eevis.codes&lt;/a&gt;) or send &lt;a href=&quot;https://github.com/eevajonnapanula/neule.art/issues/new?assignees=&amp;amp;labels=&amp;amp;template=feature_request.md&amp;amp;title=&quot;&gt;a feature request through Github&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lopidesign.is/en/shop/adults/sweaters/riddari/&quot;&gt;Riddari-sweaters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://neule.art/&quot;&gt;Neule.art&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/neule.art/&quot;&gt;Neule.art repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cheerio.js.org/&quot;&gt;cheerio&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/neule.art/blob/main/helpers/getColors.js&quot;&gt;getColors.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-06-30/how-i-created-neule-art/hello@eevis.codes&quot;&gt;hello@eevis.codes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Send &lt;a href=&quot;https://github.com/eevajonnapanula/neule.art/issues/new?assignees=&amp;amp;labels=&amp;amp;template=feature_request.md&amp;amp;title=&quot;&gt;a feature request through Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>How Can Backend Developers Improve Accessibility?</title>
    <link href="https://eevis.codes/blog/2022-07-07/how-can-backend-developers-improve-accessibility/" />
    <updated>2023-01-03T08:57:43.114Z</updated>
    <id>https://eevis.codes/blog/2022-07-07/how-can-backend-developers-improve-accessibility/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2ghMzKTGsUr7D5qf3TX3Ny/16db80018cca3f15906503a3dd923856/Twitter_post_-_9solving-non-coding-problems.png"/>]]>
      &lt;p&gt;This question might feel a bit strange. Backend developers often touch the UI less, so they don&amp;#39;t need to care, right? I beg to differ. This blog post will look at some practices backend developers can do to ensure the accessibility of the end product and documentation.&lt;/p&gt;
&lt;p&gt;We&amp;#39;ll look into API structure, documentation, performance aspects,  and backend rendered pages. This is in no way an exhaustive list of things, and there is definitely more backend devs can do, but let&amp;#39;s start with these four.&lt;/p&gt;
&lt;h2 id=&quot;api-structure&quot;&gt;API structure&lt;/h2&gt;
&lt;p&gt;There is actually a huge opportunity to improve accessibility when building APIs. This opportunity comes from reusable structures and ensuring that the content requires accessible roles, names, states, or properties. Also, APIs can enforce requiring things like having captions and transcripts for videos and audio content. &lt;/p&gt;
&lt;p&gt;One concrete example of API supporting accessibility is images. When uploading an image, the API should require an alternative text or information that the image is decorative. So, when validating the sent input, there should be a check for having the alt-text (for example, as a separate parameter). And if the image is purely decorative, and thus the alt-text is not needed, then, for example, a boolean value indicating that. &lt;/p&gt;
&lt;p&gt;Localization is another thing that helps with accessibility. Enabling people to use products in their native language can make barriers smaller. When designing an API, developers should consider localization and how that API can support it. I know there are cases where another service handles localization, or it&amp;#39;s handled in the front end, but it&amp;#39;s rarely only about the texts on the site. Often values stored in the database should be localizable too. &lt;/p&gt;
&lt;h2 id=&quot;documentation&quot;&gt;Documentation&lt;/h2&gt;
&lt;p&gt;Another aspect where backend developers can help with accessibility is the documentation. There are two sides to this: The site or platform, and the content.&lt;/p&gt;
&lt;h3 id=&quot;platform-or-site&quot;&gt;Platform or Site&lt;/h3&gt;
&lt;p&gt;First of all, the site where the documentation is should be accessible. So, for example, the site uses semantic HTML, is keyboard navigable, has colors that have enough color contrast, and has a logical and clear structure. You can read more about &lt;a href=&quot;https://www.w3.org/WAI/fundamentals/accessibility-intro/&quot;&gt;web accessibility from Web Accessibility Initiative&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Many projects use readymade solutions to render documentation. Whether it&amp;#39;s a Swagger-type documentation containing only endpoints and inputs, or a more extensive platform with text, it&amp;#39;s not usually built from scratch. So, it&amp;#39;s essential to check that the platform actually is accessible. &lt;/p&gt;
&lt;p&gt;You can &lt;a href=&quot;https://dev.to/eevajonnapanula/automated-accessibility-testing-is-a-good-start-but-you-need-to-test-manually-too-13f2&quot;&gt;use automated testing tools&lt;/a&gt; and also &lt;a href=&quot;https://eevis.codes/blog/2021-05-28/dont-develop-just-for-yourself-a-developers-checklist-to-accessibility/&quot;&gt;do some manual checks&lt;/a&gt;. These blogposts contain resources for development, but there are tools and assessments you can do for any site.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m in the process of doing some quick accessibility tests for different documentation tools, and I will write a blog post about the results. So stay tuned!&lt;/p&gt;
&lt;h3 id=&quot;content&quot;&gt;Content&lt;/h3&gt;
&lt;p&gt;Another important thing is that the actual language in the documentation is accessible. You can use some of the principles of &lt;a href=&quot;https://www.plainlanguage.gov/about/definitions/&quot;&gt;plain language&lt;/a&gt;. Let&amp;#39;s look at what this means in practice.&lt;/p&gt;
&lt;p&gt;If the documentation has longer text blocks, try to follow these instructions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When you address the reader, use &amp;quot;you,&amp;quot; not passive.&lt;/li&gt;
&lt;li&gt;Avoid complex sentences and unnecessary words.&lt;/li&gt;
&lt;li&gt;Write the most important information first.&lt;/li&gt;
&lt;li&gt;Write short sentences and paragraphs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, if the documentation has images, remember to add alternative texts explaining the image&amp;#39;s message. If the picture is purely decorative, add &lt;code&gt;alt=&amp;quot;&amp;quot;&lt;/code&gt; to it. Here&amp;#39;s &lt;a href=&quot;https://webaim.org/techniques/alttext/&quot;&gt;more about alternative texts to images&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;If you add videos to the documentation, add captions and a transcript for the video. Many people tend to watch videos without a sound on, so it serves all. The Deaf, Hard of Hearing, and Deafblind depend on the captions and transcripts to know what&amp;#39;s happening in the video. Here&amp;#39;s a resource where you can &lt;a href=&quot;https://webaim.org/techniques/captions/&quot;&gt;learn more about captions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, it&amp;#39;s good to pay attention to the structure of the content. Use headings correctly, meaning when you have a title, annotate it as such. Also, use correct heading levels. I just found a good tool for content creators to check some accessibility aspects, and I think it would work with documentation as well. The tool is called &lt;a href=&quot;https://sa11y.netlify.app/&quot;&gt;Sa11y&lt;/a&gt;. Go and check it out!&lt;/p&gt;
&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;
&lt;p&gt;Another aspect to consider is performance. It is essential that the frontend does as few heavy calculations as possible. Not everyone has the latest devices with lots of memory and calculation power. &lt;/p&gt;
&lt;p&gt;I know some people argue that this doesn&amp;#39;t really matter. But it actually does. It matters for the user. If many things are happening in the front end, the user&amp;#39;s internet is slow, and they have an older device, this might mean that nothing happens for them for a long time. That, in turn, means the site doesn&amp;#39;t work for them.&lt;/p&gt;
&lt;p&gt;If the site is something they can leave, they probably will. But what if they still need to use the site? Let&amp;#39;s say it&amp;#39;s the only way to book an appointment with a doctor, which they desperately need. If the site is slow and unresponsive, they might start trying to click things to see if it works. And when it finally starts working, all those things happen - and they find themselves in a totally wrong place. That&amp;#39;s frustrating. But it can also be a huge barrier. Many don&amp;#39;t just have the bandwidth needed to fight these non-working sites.&lt;/p&gt;
&lt;p&gt;I want to remind you that many disabled people live in poverty. For example, in the United States, about 26% of disabled people lived in poverty in 2019. (Source: &lt;a href=&quot;https://www.disabilitystatistics.org/reports/acs.cfm?statistic=7&quot;&gt;Disability Statistics.&lt;/a&gt;) They might not have the newest iPhone or the most high-performance computers. &lt;/p&gt;
&lt;p&gt;And even if you don&amp;#39;t live in poverty, it doesn&amp;#39;t mean you&amp;#39;ll always have the newest and most performant devices. I think this is often a bias we developers have - &amp;quot;Works on my computer&amp;quot; is so much more than being unable to reproduce a bug. It&amp;#39;s also a privilege because we have our macbook pros and the newest phones to work with, and we build apps that work with them.&lt;/p&gt;
&lt;h2 id=&quot;backend-rendered-pages&quot;&gt;Backend-rendered pages&lt;/h2&gt;
&lt;p&gt;Another factor for the backend developers and accessibility is backend-rendered pages. They need to be accessible, too, like any client-side rendered pages. &lt;/p&gt;
&lt;p&gt;Okay, I know; frontend developers handle the back-end-rendered pages in many cases. But there are always some times when backend devs need to create small pages. Those pages need to be accessible too. &lt;/p&gt;
&lt;p&gt;So, even if you&amp;#39;re a backend developer, I&amp;#39;d suggest looking into web accessibility and learning the basics. If you need to choose one topic to start with, I&amp;#39;d say semantic HTML. Not everything is a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; - many other elements communicate meaning to assistive technology and work better. From there, I&amp;#39;d suggest continuing with keyboard navigation and other ways users use their devices - not everyone uses a mouse.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So, in this blog post, I&amp;#39;ve been discussing ways backend developers can improve accessibility. There are four points I&amp;#39;ve been talking about: API structure, API documentation, performance, and backend rendered pages. &lt;/p&gt;
&lt;p&gt;These are not the only ways a backend developer can (and why they should) contribute to accessibility. Do you have some ways in mind? I&amp;#39;d love to hear your thoughts!&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/fundamentals/accessibility-intro/&quot;&gt;Web accessibility from Web Accessibility Initiative&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/eevajonnapanula/automated-accessibility-testing-is-a-good-start-but-you-need-to-test-manually-too-13f2&quot;&gt;Use automated testing tools&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2021-05-28/dont-develop-just-for-yourself-a-developers-checklist-to-accessibility/&quot;&gt;Do some manual checks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.plainlanguage.gov/about/definitions/&quot;&gt;Plain language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.disabilitystatistics.org/reports/acs.cfm?statistic=7&quot;&gt;Disability Statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webaim.org/techniques/alttext/&quot;&gt;More about alternative texts to images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webaim.org/techniques/captions/&quot;&gt;Learn more about captions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>On the Edge of Burnout... Again</title>
    <link href="https://eevis.codes/blog/2022-07-15/on-the-edge-of-burnout-again/" />
    <updated>2023-01-03T08:57:41.542Z</updated>
    <id>https://eevis.codes/blog/2022-07-15/on-the-edge-of-burnout-again/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/7sSwdRJS4RixwXYHsXoM2s/f625a103ec91a68f2bad72eb07c14817/Twitter_post_-_10on-the-edge-of-burnout.png"/>]]>
      &lt;p&gt;I&amp;#39;ve been super tired during the spring. Again. I could write a blog post about how disappointed I am that I&amp;#39;ve let myself into this situation - again. But this blog post is not about it. It&amp;#39;s about how I got further away from that edge and started finding myself - and the spark - again. &lt;/p&gt;
&lt;p&gt;So, to give some context: About eight months ago, I wrote &lt;a href=&quot;https://eevis.codes/blog/2021-11-22/first-month-as-an-accessibility-specialist-at-oura/&quot;&gt;a blog post about my first month as an accessibility specialist at Oura&lt;/a&gt;. At that point, I was still in the honeymoon phase, and the realities of being the first-ever accessibility specialist in the company weren&amp;#39;t apparent. &lt;/p&gt;
&lt;p&gt;You see, being the first in any role, especially in a role whose goal is purely to change existing things, is not easy. If there are any miscommunications about the position, it makes it even harder. And if you don&amp;#39;t know what&amp;#39;s expected of you, well, that&amp;#39;s certainly a risk for burnout.&lt;/p&gt;
&lt;p&gt;I should have known better - hey, I&amp;#39;ve been here before, and I know I&amp;#39;m more at risk because of my brain injury than someone else without the same history. &lt;/p&gt;
&lt;p&gt;I realized I&amp;#39;d lost my spark. Not just for the work but also for anything outside the job. I used to love sharing what I&amp;#39;ve learned, but writing and submitting proposals for conferences didn&amp;#39;t feel good. I thought, &amp;quot;What&amp;#39;s the point? Nothing&amp;#39;s going to change anyway&amp;quot;. &lt;/p&gt;
&lt;p&gt;Now that I&amp;#39;m in a better position, I&amp;#39;m a bit sad because I missed some conference applying deadlines I had been waiting for for a long time. For example, I didn&amp;#39;t submit a proposal to Inclusive Design 24 - I just didn&amp;#39;t have the energy. &lt;/p&gt;
&lt;h2 id=&quot;im-better-now&quot;&gt;I&amp;#39;m better now&lt;/h2&gt;
&lt;p&gt;I&amp;#39;m better now. But it took some time, conversations, and challenging moments before I could be honest about the situation. And again, I could use the word &amp;quot;again.&amp;quot; I&amp;#39;ve been here before.&lt;/p&gt;
&lt;p&gt;I tried to raise the problems throughout the spring. I think it was a short sickness leave that made the people at the company understand that this is pretty serious. &lt;/p&gt;
&lt;p&gt;After that, we had multiple conversations about my role. I understood some miscommunications about my position. I had a pretty different idea of the role than it actually was. &lt;/p&gt;
&lt;p&gt;Understanding what was expected of me and the whole situation better helped me. Furthermore, having some time off out in the sea helped. (I spent time sailing and kayaking. Finnish nature is amazing.)&lt;/p&gt;
&lt;p&gt;I also read two blog posts that made me think a lot about my relationship with accessibility. I&amp;#39;ll share my thoughts about them next.&lt;/p&gt;
&lt;h2 id=&quot;progress-over-perfection&quot;&gt;Progress over perfection&lt;/h2&gt;
&lt;p&gt;Working in a company, which is not very mature when it comes to accessibility, needs a specific type of attitude. Even though I knew that I couldn&amp;#39;t (or can&amp;#39;t) change everything instantly, I think I still had this idea of being able to change some visible things and doing it fast. And that was definitely a contributor to me being on the edge of burnout. &lt;/p&gt;
&lt;p&gt;And I felt (and still feel) like I have responsibility. I&amp;#39;m the company&amp;#39;s first (and the only) accessibility specialist, so I think I should be able to do something visible. To change something. Like how we don&amp;#39;t add alternative texts to tweets, even the most important ones. &lt;/p&gt;
&lt;p&gt;Meryl Evans has been talking about progress over perfection - and in her blog post, &lt;a href=&quot;https://meryl.net/accessibility-progress-not-perfection/&quot;&gt;&amp;quot;Accessibility: Why You Need to Work Toward Progress Over Perfection&amp;quot;&lt;/a&gt;, she writes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Accessibility isn&amp;#39;t all or nothing. It&amp;#39;s progress, not perfection. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I try to remember that. I try to find those good things, things that we do right. For example, we have some outstanding professionals who contribute to accessibility from their positions - for instance, Janne Käki, who does a fantastic job with iOS development. And that definitely is progress. &lt;/p&gt;
&lt;h2 id=&quot;regaining-my-accessibility-spark&quot;&gt;Regaining my accessibility spark&lt;/h2&gt;
&lt;p&gt;As mentioned earlier in the blog post, I felt like I had lost my (accessibility) spark. Everything felt more or less pointless. I felt that whatever I do, someone always tells me, &amp;quot;No, accessibility doesn&amp;#39;t matter.&amp;quot; Ok, usually, the words were not that direct. And it wasn&amp;#39;t just about work - it was also outside it. &lt;/p&gt;
&lt;p&gt;I read Sheri Bryne-Haber&amp;#39;s blog post &lt;a href=&quot;https://uxdesign.cc/regaining-your-accessibility-spark-a182eee9e8e6&quot;&gt;&amp;quot;Regaining your accessibility spark&amp;quot;&lt;/a&gt;. Initially, I thought it had some good advice. I didn&amp;#39;t act on it. But I kept coming back to that post. &lt;/p&gt;
&lt;p&gt;Some things on the list have helped me a lot this time. I needed to remind myself of the &amp;quot;why&amp;quot;: why I do what I do. &lt;/p&gt;
&lt;p&gt;I also needed (and still need) to remind myself about the progress, about the (little) things I&amp;#39;ve accomplished. Like the audit on our website and how the bug fixes from that audit are moving forward - they&amp;#39;re not just visible yet. &lt;/p&gt;
&lt;p&gt;And I sought help. I&amp;#39;ve had this incredible occupational psychologist with whom I&amp;#39;ve been talking about the work and how to improve it. To discuss with someone, to go through things with someone outside of the situation (but who knows the circumstances), has helped me tremendously. &lt;/p&gt;
&lt;p&gt;Now that the situation is better, I have focused on things other than work. Sheri Bryne-Haber suggests things like finding accessibility mentoring/volunteering gigs, writing a blog or a book, or learning a new language, gaining a new hobby, or investing more in a hobby you already have. &lt;/p&gt;
&lt;p&gt;And now that I think of it... I&amp;#39;ve done all three. I&amp;#39;m doing some volunteering. I&amp;#39;ve also been writing to my blog more (and I might have some ideas about the book part, too) and investing more in hobbies I already have - knitting and kayaking. As for investing, lately I&amp;#39;ve spent much more money on them than I have been for a while. But also time!&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So, I&amp;#39;ve been on the edge of burnout - again. In this blog post, I shared how I got further away from that edge and how I&amp;#39;ve been finding my accessibility spark again. It&amp;#39;s been a journey, and that journey continues. &lt;/p&gt;
&lt;p&gt;Do you have similar experiences? Did you lose your spark and regain it? &lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2021-11-22/first-month-as-an-accessibility-specialist-at-oura/&quot;&gt;A blog post about my first month as an accessibility specialist at Oura&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://meryl.net/accessibility-progress-not-perfection/&quot;&gt;&amp;quot;Accessibility: Why You Need to Work Toward Progress Over Perfection&amp;quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://uxdesign.cc/regaining-your-accessibility-spark-a182eee9e8e6&quot;&gt;&amp;quot;Regaining your accessibility spark&amp;quot;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>My Advice to a Developer New to Accessibility</title>
    <link href="https://eevis.codes/blog/2022-07-22/my-advice-to-a-developer-new-to-accessibility/" />
    <updated>2023-01-03T08:57:41.203Z</updated>
    <id>https://eevis.codes/blog/2022-07-22/my-advice-to-a-developer-new-to-accessibility/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1lELvZGBKzLv3PrJkUSC82/4f8b9a4d56595a63f1e33d9aa49404de/Twitter_post_-_11solving-non-coding-problems.png"/>]]>
      &lt;p&gt;Learning about accessibility for the first time can feel overwhelming. You start reading, and at some point, it hits you: There is so much to learn! And if you&amp;#39;re like me and feel that it&amp;#39;s your responsibility to make the things you create accessible, that might even make you feel anxious. &lt;/p&gt;
&lt;p&gt;And yes, there is a lot to learn regarding accessibility. But you don&amp;#39;t need to know everything at once. As Meryl Evans reminds us in her tweet (and a blog post the tweet links to), it&amp;#39;s about progress over perfection: &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/merylkevans/status/1547635550466674692&quot;&gt;https://twitter.com/merylkevans/status/1547635550466674692&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, here are some things I wish I had known when I first started learning accessibility. Heck, I wish I had known these things when I started learning web development! I&amp;#39;ll share semantic HTML, keyboard navigation, and listening to people with disabilities. &lt;/p&gt;
&lt;h2 id=&quot;learn-semantic-html-and-use-it&quot;&gt;Learn Semantic HTML and Use It&lt;/h2&gt;
&lt;p&gt;So, first thing: Learn semantic HTML. But what does it mean? Semantic HTML, or semantic markup, describes its meaning to the browser and developer in a human- and machine-readable way. So, with semantic elements, a human will know what the HTML element is about, and the browser knows what it should render and how it should behave when a user interacts with it.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s an example: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button onClick={...}&amp;gt;I&amp;#39;m an honest button&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That button uses a semantic &lt;code&gt;button&lt;/code&gt;-element. When it does so, the browser knows what it should do when the user either clicks or activates it with a keyboard or other input device. Here&amp;#39;s an example of a non-semantic &amp;quot;button&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div className=&amp;quot;button&amp;quot; onClick={...}&amp;gt;
    I look like a button
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, this &amp;quot;button&amp;quot; made out of &lt;code&gt;div&lt;/code&gt; looks like a button and even has the &lt;code&gt;onClick&lt;/code&gt;-handler. So, wouldn&amp;#39;t a human recognize it as a button? Well, yes and no. It depends on the human. &lt;/p&gt;
&lt;p&gt;You see, this &amp;quot;button&amp;quot; works only for mouse users. As &lt;code&gt;div&lt;/code&gt; is not an interactive element by nature, it doesn&amp;#39;t handle interaction the same way as, for example, a &lt;code&gt;button&lt;/code&gt;-element. When using a semantic button-element, that onClick-event propagates such that it handles keyboard interaction as well. However, &lt;code&gt;div&lt;/code&gt;s don&amp;#39;t do such a thing.&lt;/p&gt;
&lt;p&gt;Another reason a non-mouse user will recognize that it&amp;#39;s not a button is that you can&amp;#39;t focus on it. That means the button is not in the focus/tab order. And that, in turn, means that a non-mouse user can&amp;#39;t interact with it. &lt;/p&gt;
&lt;p&gt;There are lots of semantic elements in the HTML. For some reason, we tend to use a &lt;code&gt;div&lt;/code&gt; for many things. Did you know, for example, that you can annotate addresses with an &lt;code&gt;address&lt;/code&gt;-element? Or wrap dates or times with the &lt;code&gt;time&lt;/code&gt;-element? Or display progress with a &lt;code&gt;progress&lt;/code&gt;-element? &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element#embedded_content&quot;&gt;MDN lists HTML elements&lt;/a&gt;, so be sure to check that list. &lt;/p&gt;
&lt;p&gt;If you want to read about other benefits of using semantic HTML, I wrote a blog post &lt;a href=&quot;https://dev.to/eevajonnapanula/ode-to-semantic-html-38c3&quot;&gt;&amp;quot;Ode to Semantic HTML&amp;quot;&lt;/a&gt; a while back. &lt;/p&gt;
&lt;h2 id=&quot;learn-about-keyboard-navigation&quot;&gt;Learn About Keyboard Navigation&lt;/h2&gt;
&lt;p&gt;Another piece of advice I want to give is something we touched on in the previous section: Learn about keyboard navigation.&lt;/p&gt;
&lt;h3 id=&quot;what-and-why-of-keyboard-navigation&quot;&gt;What And Why of Keyboard Navigation&lt;/h3&gt;
&lt;p&gt;Now you might wonder, &amp;quot;Why would anyone use keyboard to navigate?&amp;quot;. Well, first of all: not everyone can use a mouse, or they just don&amp;#39;t want to. Sometimes doing tasks on a computer is way faster with, for example, a keyboard. &lt;/p&gt;
&lt;p&gt;There are lots of different ways of using digital devices. Some people use a tool called a screen reader, which reads the screen&amp;#39;s contents out loud. Some use a keyboard for navigation because they can&amp;#39;t use a mouse. And some people utilize assistive tools like mouth sticks, custom keyboards, switch devices, and other tools to use digital devices. And let&amp;#39;s not forget voice interfaces or eye-tracking technologies, which some people use.&lt;/p&gt;
&lt;p&gt;So the point is that there are multiple other ways to interact with a digital device than just a mouse. And we, as developers, need to create interfaces that work for all of our users and not exclude anyone.&lt;/p&gt;
&lt;p&gt;Now you might feel overwhelmed. &amp;quot;How can I create websites for all those needs?&amp;quot; Well, I have good news: You don&amp;#39;t need to think about each different input method. Many of the input methods mentioned above emulate what a plain ol&amp;#39; keyboard does. &lt;/p&gt;
&lt;p&gt;My suggestion is: first, learn how keyboard navigation should work on a website. After you&amp;#39;ve learned that, it&amp;#39;s time to learn about the specifics of other input methods.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s talk a bit more about keyboard interaction. There are two things I urge you to learn and exercise: Keyboard navigation patterns and how to test them (and regularly do so).&lt;/p&gt;
&lt;h3 id=&quot;keyboard-navigation-patterns&quot;&gt;Keyboard Navigation Patterns&lt;/h3&gt;
&lt;p&gt;So, how does keyboard navigation work? When users want to go forward, they press the &lt;kbd&gt;Tab&lt;/kbd&gt;-key, and this takes (or should take, depending on how well the website has been coded) them to the next interactive element. That means a link, button, input field, or similar - and not, for example, a heading.&lt;/p&gt;
&lt;p&gt;When keyboard users want to read something or to navigate forward on a site without interactive elements, they scroll with arrow keys. I want to emphasize that because I see a lot of websites where a developer has added &lt;code&gt;tabIndex&lt;/code&gt;-attribute to non-interactive elements so keyboard users could navigate forward. &lt;/p&gt;
&lt;p&gt;It&amp;#39;s a great thing that a developer thinks about keyboard users. This pattern, however, increases the amount of the tab stops to reach the actual interactive elements. For some, it&amp;#39;s frustrating, but for some, it can even be painful if, for example, each keypress causes pain.&lt;/p&gt;
&lt;p&gt;Okay, back to patterns. When a keyboard user wants to navigate to the previous interactive element, they use &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;Tab&lt;/kbd&gt;. &lt;/p&gt;
&lt;p&gt;There are different patterns for activating interactive elements. I&amp;#39;ll list a few of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;: activates with &lt;kbd&gt;Enter&lt;/kbd&gt; or &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;: activates with &lt;kbd&gt;Enter&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; /&amp;gt;&lt;/code&gt;: toggles with &lt;kbd&gt;Space&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;radio&amp;quot; /&amp;gt;&lt;/code&gt;: &lt;kbd&gt;Tab&lt;/kbd&gt; in (and out) the radio input group, arrow-keys to change the value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you can see, buttons and links are activated with different keys. Specifically, the spacebar key doesn&amp;#39;t activate a link. This distinction between the elements is good to remember - when users see something like a button, they think it works as a button. If the underlying component is a link, it doesn&amp;#39;t activate with &lt;kbd&gt;Space&lt;/kbd&gt; but rather scrolls down on the page. That&amp;#39;s super frustrating.&lt;/p&gt;
&lt;p&gt;You can find the expected keyboard navigation patterns for more complex user interface element patterns from &lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns/&quot;&gt;WAI-ARIA Authoring practices&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;testing-with-keyboard&quot;&gt;Testing with Keyboard&lt;/h3&gt;
&lt;p&gt;When you build user interfaces, be sure to test with a keyboard. The basic method is to navigate through the whole site using the keyboard. When you encounter an interactive element, try to trigger it. Check that it works as expected - meaning that you can do everything with a keyboard that you can with a mouse. &lt;/p&gt;
&lt;p&gt;Oh, and one note if you&amp;#39;re using Mac and Safari. You need to enable keyboard navigation for them - I don&amp;#39;t know why, but it doesn&amp;#39;t work out of the box. Here are &lt;a href=&quot;https://www.a11yproject.com/posts/macos-browser-keyboard-navigation/&quot;&gt;instructions for enabling keyboard navigation for Mac and Safari.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;listen-to-people-with-disabilities&quot;&gt;Listen to People with Disabilities&lt;/h2&gt;
&lt;p&gt;The final advice I will give in this blog post is to listen to disabled people. This advice consists of two things: Listening and learning about their (our) life in general and listening to the feedback we/they give. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ll share some resources I&amp;#39;ve found helpful and informative. There&amp;#39;s plenty more out there on the internet, and if you think something should be included, I definitely want to know about them!&lt;/p&gt;
&lt;p&gt;One of the good places to go for these stories is Twitter. There are a couple of hashtags I recommend following:
    - &lt;a href=&quot;https://twitter.com/search?q=actuallyautistic&amp;amp;src=typed_query&amp;amp;f=live&quot;&gt;#ActuallyAutistic&lt;/a&gt;
    - &lt;a href=&quot;https://twitter.com/hashtag/EverydayAbleism?src=hashtag_click&amp;amp;f=live&quot;&gt;#EverydayAbleism&lt;/a&gt;
    - &lt;a href=&quot;https://twitter.com/hashtag/DisabilityPrideMonth?src=hashtag_click&amp;amp;f=live&quot;&gt;#DisabilityPrideMonth&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;#39;d love to add some more here, so if you know some other good hashtags, let me know.&lt;/p&gt;
&lt;p&gt;Also, a good resource for experiences and expectations of disabled people is &lt;a href=&quot;https://a11yrules.com/series/a11y-rules-soundbite/&quot;&gt;Nicolas Steenhouts&amp;#39; A11y Rules Soundbite-podcast series&lt;/a&gt;. These short discussions with disabled people give a lot of good points of view for designing and building the web.  &lt;/p&gt;
&lt;p&gt;I also want to mention a brilliant book from &lt;a href=&quot;https://emilyladau.com/book/&quot;&gt;Emily Ladau: Demystifying Disability&lt;/a&gt;. I actually wrote some thoughts from it once I had read it, so check that blog post too: &lt;a href=&quot;https://eevis.codes/blog/2021-11-06/thoughts-from-reading-demystifying-disability-by-emily-ladau/&quot;&gt;&amp;quot;Thoughts From Reading Demystifying Disability by Emily Ladau&amp;quot;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Another great resource is &lt;a href=&quot;https://disabilityvisibilityproject.com/&quot;&gt;Disability Visibility-project&lt;/a&gt;. It&amp;#39;s defined on the About-page with the following words:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Disability Visibility Project is an online community dedicated to creating, sharing, and amplifying disability media and culture.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Do you have some other resources? Or other advice an accessibility-novice developer should know about?	&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element#embedded_content&quot;&gt;MDN lists HTML elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/eevajonnapanula/ode-to-semantic-html-38c3&quot;&gt;&amp;quot;Ode to Semantic HTML&amp;quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns/&quot;&gt;WAI-ARIA Authoring practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.a11yproject.com/posts/macos-browser-keyboard-navigation/&quot;&gt;Instructions for enabling keyboard navigation for Mac and Safari.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/search?q=actuallyautistic&amp;amp;src=typed_query&amp;amp;f=live&quot;&gt;#ActuallyAutistic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/hashtag/EverydayAbleism?src=hashtag_click&amp;amp;f=live&quot;&gt;#EverydayAbleism&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/hashtag/DisabilityPrideMonth?src=hashtag_click&amp;amp;f=live&quot;&gt;#DisabilityPrideMonth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://a11yrules.com/series/a11y-rules-soundbite/&quot;&gt;Nicolas Steenhouts&amp;#39; A11y Rules Soundbite-podcast series&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://emilyladau.com/book/&quot;&gt;Emily Ladau: Demystifying Disability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2021-11-06/thoughts-from-reading-demystifying-disability-by-emily-ladau/&quot;&gt;&amp;quot;# Thoughts From Reading Demystifying Disability by Emily Ladau&amp;quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://disabilityvisibilityproject.com/&quot;&gt;Disability Visibility-project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Don&#39;t Set outline: 0 or outline: none for Focus-Styles</title>
    <link href="https://eevis.codes/blog/2022-07-28/dont-set-outline-0-or-outline-none-for-focus/" />
    <updated>2023-01-03T08:57:40.850Z</updated>
    <id>https://eevis.codes/blog/2022-07-28/dont-set-outline-0-or-outline-none-for-focus/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/31C2z6kWyHsyO2m39XI8bM/daf07c5464a813c8cb47304611eeae70/Twitter_post_-_12solving-non-coding-problems__1_.png"/>]]>
      &lt;p&gt;&amp;quot;The focus indicator is ugly, and I will remove it. No, this is not up for discussion.&amp;quot; These are some lines from a conversation with a designer-developer some years back. The theme of the conversation was a website they were working with.&lt;/p&gt;
&lt;p&gt;I tried to explain the problem with non-existing focus styles. Still, they insisted their view of beauty was more important than the customer&amp;#39;s right to a working website. &lt;/p&gt;
&lt;p&gt;And this has not been the only conversation I&amp;#39;ve had around focus styles. Some people want, stubbornly, to remove all focus styles because they (as a mouse user) don&amp;#39;t like those focus rings.&lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;ll share why these visible focus styles are so important - and why you should never set &lt;code&gt;outline&lt;/code&gt;-property to &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt; for focus styles. But let&amp;#39;s first talk about the property itself. &lt;/p&gt;
&lt;h2 id=&quot;what-is-outline&quot;&gt;What is &lt;code&gt;outline&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;outline&lt;/code&gt; is a CSS property, or actually, a CSS shorthand property. That means that you can set multiple properties with it. These properties are &lt;code&gt;outline-color&lt;/code&gt;, &lt;code&gt;outline-style&lt;/code&gt;, and &lt;code&gt;outline-width.&lt;/code&gt; You can set one, two, or three attributes simultaneously. Here&amp;#39;s an example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.className {
  outline: 2px solid red;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the outline is set to be a solid red line, which is 2px wide.&lt;/p&gt;
&lt;p&gt;In the box model, &lt;code&gt;outline&lt;/code&gt; is set &lt;em&gt;outside&lt;/em&gt; the box&amp;#39;s border edge and does not add to the element&amp;#39;s size, meaning it doesn&amp;#39;t take any space from the page layout.&lt;/p&gt;
&lt;p&gt;You can modify the looks of the outline with two more properties: &lt;code&gt;outline-offset&lt;/code&gt;, which affects how far from the border of an element the outline is, and &lt;code&gt;border-radius&lt;/code&gt;, which modifies the radius of the border. It affects the whole element, not just outline.&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/outline&quot;&gt;read more about the &lt;code&gt;outline&lt;/code&gt; in MDN.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;outline-and-focus&quot;&gt;&lt;code&gt;outline&lt;/code&gt; and Focus&lt;/h2&gt;
&lt;p&gt;The default styles for focus indicators are implemented with the &lt;code&gt;outline&lt;/code&gt;-property. Every browser has its own default styles for this focus indicator.&lt;/p&gt;
&lt;p&gt;These default styles are defined with user agent stylesheets. Jens Oliver Meier has written more about them, if you&amp;#39;re interested: &lt;a href=&quot;https://meiert.com/en/blog/user-agent-style-sheets/&quot;&gt;&amp;quot;User Agent Style Sheets: Basics and Samples&amp;quot;&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Here are examples of default focus indicators for Chrome, Firefox, and Safari, as seen on Mac:&lt;/p&gt;
&lt;p&gt;Chrome:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/sk24pdnnlwhc/4F7kQwDJXFg3k9UE0h9MC9/2c180877e24f5a724f394d785ed674e2/Screenshot_2022-07-28_at_6.28.45.png&quot; alt=&quot;Link with text &amp;quot;Beauty of Finland&amp;#39;s national parks still drawing crowds after pandemic peaks&amp;quot; in blue, and a darker blue outline surrounding the text.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Firefox:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/sk24pdnnlwhc/7vgr80VHtXe4tIKrBrLJhY/8c1da50d8d8395f1eb88360526c69351/Screenshot_2022-07-28_at_6.30.56.png&quot; alt=&quot;Link with text &amp;quot;Beauty of Finland&amp;#39;s national parks still drawing crowds after pandemic peaks&amp;quot; in blue in two rows, and a semi-blue outline surrounding each of the lines.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Safari:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/sk24pdnnlwhc/5cjC6GFzzlMpOnJCDRZSrw/7d2d4721f93a8e6c1cedbb01b539cdf5/Screenshot_2022-07-28_at_6.33.10.png&quot; alt=&quot;Link with text &amp;quot;Beauty of Finland&amp;#39;s national parks still drawing crowds after pandemic peaks&amp;quot; in blue, and a light blue outline surrounding the text. The outline has a bit of padding between it and the text.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The examples are from the Finnish Broadcasting Company&amp;#39;s (Yle) website. I&amp;#39;ve disabled the author styles with the Web Developer extension. &lt;/p&gt;
&lt;p&gt;As you might notice from the examples, these styles aren&amp;#39;t apparent in most conditions. The default focus indicator styles do pass WCAG criteria about visible focus. Still, I think accessibility shouldn&amp;#39;t be just about passing the success criteria. It should be about inclusion, and thus creating more visible focus styles is a must. &lt;/p&gt;
&lt;h2 id=&quot;why-visible-focus-styles-are-important&quot;&gt;Why Visible Focus Styles Are Important?&lt;/h2&gt;
&lt;p&gt;So, why having visible focus styles is so important? It&amp;#39;s because not everyone uses a mouse. Many people prefer or need to use tools like keyboards, switch devices, or others to navigate the page. And when they do so, they don&amp;#39;t have the cursor of a mouse to tell them where they are on the page - they rely on focus styles.&lt;/p&gt;
&lt;p&gt;So, to put it short - not having visible focus styles is like using a website with a mouse, but the cursor is invisible. &lt;/p&gt;
&lt;h2 id=&quot;but-what-if-i-have-better-focus-styles&quot;&gt;But What If I Have Better Focus Styles?&lt;/h2&gt;
&lt;p&gt;The outline completely disappears when you set &lt;code&gt;outline&lt;/code&gt; to none or 0. Now you might ask, &amp;quot;But what if I have better focus styles? Why can&amp;#39;t I remove the outline then?&amp;quot; &lt;/p&gt;
&lt;p&gt;The problem is that it removes the outline from everywhere - also, from, for example, Windows High Contrast Mode (WHCM) users. WHCM works by removing the background colors and images and replacing text color (and some other colors) with the selected theme&amp;#39;s colors. That affects things like &lt;code&gt;box-shadow&lt;/code&gt; - it&amp;#39;s not visible at all. And that, in turn, means that most of the enhanced focus styles won&amp;#39;t appear.&lt;/p&gt;
&lt;p&gt;&amp;quot;So, I&amp;#39;m stuck with a visible outline, then?&amp;quot; I have good news: No, you&amp;#39;re not! You can actually use the &lt;code&gt;transparent&lt;/code&gt;-keyword like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.some-element:focus {
  outline: 1px solid transparent;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That will show up in WHCM as it forces the colors on existing outlines and will be transparent in other cases. And also, because the outline doesn&amp;#39;t take up space on the boxes, the transparent outline will be invisible in other cases.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So, the gist of this blog post is: never set &lt;code&gt;outline&lt;/code&gt;-property to &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt; for focus styles, use the &lt;code&gt;transparent&lt;/code&gt;-keyword for the color in these cases. And if you do this, remember to add (more) visible focus styles via the chosen alternative method.&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/outline&quot;&gt;Read more about the &lt;code&gt;outline&lt;/code&gt; in MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://meiert.com/en/blog/user-agent-style-sheets/&quot;&gt;&amp;quot;User Agent Style Sheets: Basics and Samples&amp;quot;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Notes of a Web Developer Learning Kotlin and Android Development - part 1</title>
    <link href="https://eevis.codes/blog/2022-08-02/notes-of-a-web-developer-learning-kotlin-and-android-development-part-1/" />
    <updated>2023-01-03T08:57:39.902Z</updated>
    <id>https://eevis.codes/blog/2022-08-02/notes-of-a-web-developer-learning-kotlin-and-android-development-part-1/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/5VbETfRk638r5exCu5Effp/86001841e80928ddb9ee5c0463b65057/Kotlin-Android-part1on-the-edge-of-burnout.png"/>]]>
      &lt;p&gt;First, a bit of background: I&amp;#39;m an accessibility specialist who recently started digging deeper into our company&amp;#39;s Android application. When auditing a web page, it&amp;#39;s easy for me to provide technical guidance on how to fix accessibility issues. However, native mobile development and accessibility have been a mystery to me. Now that I&amp;#39;ve started evaluating our native apps, I&amp;#39;ve realized I need to understand more from the technical side. &lt;/p&gt;
&lt;p&gt;Okay, I have to admit that I&amp;#39;m not a complete newbie in Android development. When I was learning to code about 6-7 years ago, one of the learning paths I was following was Android development. I was working through Google&amp;#39;s Android courses in Udacity back then but never finished them. &lt;/p&gt;
&lt;p&gt;At the time, the language for Android development was Java, and I&amp;#39;ve never felt too comfortable with it. I ended up working as a web developer (one of the other learning paths I was taking) and have been able to avoid Java ever since. Fortunately, today Java is more in the background, and Kotlin seems to be the hot thing, so I decided to learn it with Android development. &lt;/p&gt;
&lt;p&gt;In this and upcoming blog posts, I will share thoughts, learnings, and reflections from learning Kotlin and Android development. My initial plan is to work through the &lt;a href=&quot;https://developer.android.com/courses/android-basics-kotlin/course&quot;&gt;Android Basics in Kotlin&lt;/a&gt; and, after that, learn more about Android accessibility specifically. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s start with the first unit (Kotlin Basics). It is about the basics of Kotlin and Android - adding text and images and creating a simple, interactive interface. &lt;/p&gt;
&lt;h2 id=&quot;first-impressions-from-kotlin&quot;&gt;First Impressions from Kotlin&lt;/h2&gt;
&lt;p&gt;I admit I&amp;#39;ve been a bit prejudiced towards Kotlin because many people have been selling it to me with the idea that it&amp;#39;s like &amp;quot;better Java.&amp;quot; I&amp;#39;ve never particularly liked Java. &lt;/p&gt;
&lt;p&gt;However, now that I started learning Kotlin, I&amp;#39;m feeling like... Wow, why haven&amp;#39;t I tried this before? For me, it resembles Python a bit. I can&amp;#39;t exactly pinpoint why, but I get this same soft feeling when coding in Python.&lt;/p&gt;
&lt;p&gt;Okay, I do just some dabbling with Python for fun, and I&amp;#39;m rarely working with it, but I really like it. So I can wholeheartedly say that the first impressions from Kotlin were positive. &lt;/p&gt;
&lt;p&gt;I know that part of feeling like this is because I&amp;#39;ve been coding for a long time now, and I&amp;#39;ve learned at least the basics of many languages. I know JS, TS, Python, Java, Clojure, and Ruby, to name a few. And learning new (programming) languages gets easier after every new language.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s look at some concrete learnings and findings from Kotlin from the first unit. I collected the topics from the first unit and did some extra reading to understand some differences and concepts. &lt;/p&gt;
&lt;h3 id=&quot;variable-names&quot;&gt;Variable Names&lt;/h3&gt;
&lt;p&gt;The first thing to learn with a new language is usually variables and what&amp;#39;s their syntax. I collected three keywords from the first unit to define variables: &lt;code&gt;val&lt;/code&gt;, &lt;code&gt;var&lt;/code&gt;, and &lt;code&gt;const.&lt;/code&gt;&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;code&gt;val&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;code&gt;val&lt;/code&gt; is used to define read-only, local variables.  &lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;var&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;code&gt;var&lt;/code&gt; is used to define mutable variables.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;const&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;code&gt;const&lt;/code&gt; is used to define immutable values (the same as &quot;val&quot;). The difference is that these variables are known at compile time. An example from the course: &lt;code&gt;TAG&lt;/code&gt; was used to define the component name when using &lt;code&gt;Logger&lt;/code&gt;, and was set to &lt;code&gt;MainActivity&lt;/code&gt;.&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Another important keyword is &lt;code&gt;fun&lt;/code&gt;, which defines functions. An example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun petACat() {
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is straightforward, as JavaScript has a similar syntax with the &lt;code&gt;function&lt;/code&gt;-keyword. I just need to remember the different naming.&lt;/p&gt;
&lt;h3 id=&quot;some-nice-methods-and-data-types&quot;&gt;Some Nice Methods and Data Types&lt;/h3&gt;
&lt;p&gt;The course went through some interesting and useful data types (&lt;code&gt;range&lt;/code&gt;) and methods (&lt;code&gt;random&lt;/code&gt;, &lt;code&gt;when&lt;/code&gt;-statement, and &lt;code&gt;repeat&lt;/code&gt;). I know that most, or all, are present in the other languages I mentioned learning. However, I&amp;#39;m working primarily with JavaScript, so that&amp;#39;s what I&amp;#39;m comparing Kotlin to within my mind. &lt;/p&gt;
&lt;h4 id=&quot;ranges-and-random-method&quot;&gt;Ranges and Random-Method&lt;/h4&gt;
&lt;p&gt;One of the apps created on the course was a dice-throwing app. The functionality is simple: a user clicks a button, and the app shows a random number between 1 and 6. &lt;/p&gt;
&lt;p&gt;To accomplish this, the code uses a range to create a set of possible values (as the values are in a range between 1 and 6). And that range (&lt;code&gt;IntRange&lt;/code&gt;) has a method called &lt;code&gt;random&lt;/code&gt;, which, as you would guess, returns a random number that is on that range. &lt;/p&gt;
&lt;p&gt;It&amp;#39;s so much more straightforward than in JavaScript. I like it a lot. Here&amp;#39;s an example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val range = 1..6
val randomNum = range.random()
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;when---statement&quot;&gt;When - Statement&lt;/h4&gt;
&lt;p&gt;Another cool method and/or statement is the &lt;code&gt;when&lt;/code&gt;-syntax. I like the way you can, for example, define values with fewer lines of code with it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val knitGauge10Cm = when(getYarnWeight()) {
      &amp;quot;Lace&amp;quot; -&amp;gt; &amp;quot;32-34 sts&amp;quot;
      &amp;quot;Fingering&amp;quot; -&amp;gt; &amp;quot;28 sts&amp;quot;
      &amp;quot;Sport&amp;quot; -&amp;gt; &amp;quot;24-26 sts&amp;quot;
      &amp;quot;DK&amp;quot; -&amp;gt; &amp;quot;22 sts&amp;quot;
      &amp;quot;Worsted&amp;quot; -&amp;gt; &amp;quot;20 sts&amp;quot;
      &amp;quot;Aran&amp;quot; -&amp;gt; &amp;quot;18 sts&amp;quot;
      &amp;quot;Bulky&amp;quot; -&amp;gt; &amp;quot;14-15 sts&amp;quot;
      else -&amp;gt; &amp;quot;Unknown&amp;quot;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, &lt;code&gt;when&lt;/code&gt; defines a gauge for 10 cm (or 4 inches) for a knit based on yarn weight. When knitting a garment, it is crucial to know how many stitches there are in a certain width of the knit to match the intended size. So if I had an app helping to calculate the gauge for different types of yarns, this example code would be helpful.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re interested in reading more about knitting and gauges, check out &lt;a href=&quot;https://www.knitpicks.com/learning-center/gauge-guide&quot;&gt;Knit Picks&amp;#39; guide about gauge&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;repeat---method&quot;&gt;Repeat - Method&lt;/h4&gt;
&lt;p&gt;The third method I want to mention is the &lt;code&gt;repeat&lt;/code&gt;-method. It&amp;#39;s so convinient to have a method for repeating something x times. In JavaScript, you naturally can do this - but it takes more lines of code. With Kotlin, you can do that with one function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun meow(times: Int) {
  repeat(times) {
    println(&amp;quot;Meow&amp;quot;)
  }  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;getting-back-to-android-development&quot;&gt;Getting Back to Android Development&lt;/h2&gt;
&lt;p&gt;As mentioned at the beginning of the post, I was learning about Android Development years ago. Because of that, it was relatively easy to jump back to the world of Android Studio and other things. And as the first unit concentrates on fundamental things, it felt familiar and easy.&lt;/p&gt;
&lt;p&gt;However, I recognized some things I need to practice in the upcoming units. For example, it was difficult to understand the whole &lt;code&gt;ConstraintLayout&lt;/code&gt;-concept, and which constraint points to where. I think I will need to think and draw some kind of comparison to CSS layouts to understand the differences. &lt;/p&gt;
&lt;p&gt;The next unit will be about layouts, so I&amp;#39;d imagine that will get easier. Also, I&amp;#39;ll learn about user input.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, I shared my initial feelings and thoughts from learning about Kotlin and Android development. It&amp;#39;s been fun (pun intended), and I&amp;#39;m looking forward to the following units.&lt;/p&gt;
&lt;p&gt;In the next blog post, I will cover the second unit, &amp;quot;Layouts.&amp;quot; From the Kotlin side, it consists of things like lists, and from the Android side, different layouts and &lt;code&gt;ScrollView&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Have you learned native (Android) development lately? What have your experiences been? Or do you have any tips for a web developer learning about Android development?&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/courses/android-basics-kotlin/course&quot;&gt;Android Basics in Kotlin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.knitpicks.com/learning-center/gauge-guide&quot;&gt;Knit Picks&amp;#39; guide about gauge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Results of Quick Testing of Documentation Tools&#39; Accessibility</title>
    <link href="https://eevis.codes/blog/2022-08-11/results-of-quick-testing-of-documentation-tools-accessibility/" />
    <updated>2023-01-03T08:57:42.398Z</updated>
    <id>https://eevis.codes/blog/2022-08-11/results-of-quick-testing-of-documentation-tools-accessibility/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/6E7oE62pKmblXh18uQoMIp/9b3b64b6dc61a4fb08eccf58fafdb45f/Documentation_Tools_A11yon-the-edge-of-burnout__1_.png"/>]]>
      &lt;p&gt;When writing my &lt;a href=&quot;https://eevis.codes/blog/2022-07-07/how-can-backend-developers-improve-accessibility/&quot;&gt;blog post about what backend devs can do for accessibility&lt;/a&gt;, I did some quick accessibility tests for different documentation tools. Initially, I thought I&amp;#39;d write about the results in that blog post, but there was just too much to report. So, I ended up writing this blog post.&lt;/p&gt;
&lt;p&gt; I chose four tools that I know many projects are using. These tools are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Swagger&lt;/li&gt;
&lt;li&gt;Read the Docs&lt;/li&gt;
&lt;li&gt;Docusaurus&lt;/li&gt;
&lt;li&gt;GitBook&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, I ran an automated test with the aXe browser extension and dug deeper into the DOM based on the test results. After that, I tested the site with a keyboard. &lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;ll share the test results and some thoughts about them. As you can see from the steps described above, the testing was not exhaustive. I just wanted to get an overview of the issues. I did not, for example, test with a screen reader or with narrower viewports (or zooming). &lt;/p&gt;
&lt;p&gt;This was a conscious decision, partly because I didn&amp;#39;t have time and partly because this blog post would&amp;#39;ve been super long. And I think this already provides &lt;em&gt;some&lt;/em&gt; overview of the state of accessibility of these tools.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s get started.&lt;/p&gt;
&lt;h2 id=&quot;swagger&quot;&gt;Swagger&lt;/h2&gt;
&lt;p&gt;URL used for testing: &lt;a href=&quot;https://petstore3.swagger.io/&quot;&gt;https://petstore3.swagger.io/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Swagger is a set of tools used for API documentation. The Swagger API project started in 2011. Initially, it was a specification, but it was later named OpenAPI Specification. So it has a long history, and many projects use it. &lt;/p&gt;
&lt;p&gt;One of its tools is Swagger UI, a user interface for browsing the API endpoints. That&amp;#39;s the one I&amp;#39;m testing for this blog post. &lt;/p&gt;
&lt;h3 id=&quot;testing-swagger-ui-with-axe&quot;&gt;Testing Swagger UI with Axe&lt;/h3&gt;
&lt;p&gt;Runnin Axe-browser extension showed 64 issues, two of which are critical, 35 serious, 18 moderate, and 7 minor. Most of the issues (37) were related to color contrast issues. The contrast ratio for the problematic color combinations ranged between 2.03 and 3.75.&lt;/p&gt;
&lt;p&gt;Another problem was form input without programmatically associated labels. Some of them were flagged, but when I started digging deeper into the site&amp;#39;s code, I noticed that not every instance was flagged. So, there were more problems with form labels than Axe was showing.&lt;/p&gt;
&lt;p&gt;The third problem I want to raise is the lack of landmark regions. Not having these regions means that user, who needs these landmark regions for navigating, can&amp;#39;t use them. For example, screen reader users and keyboard users who use tools to navigate using landmark regions would need to either listen to a lot more items and/or press a tab many more times to be able to navigate to certain parts of the website. &lt;/p&gt;
&lt;h3 id=&quot;testing-swagger-ui-with-keyboard&quot;&gt;Testing Swagger UI with Keyboard&lt;/h3&gt;
&lt;p&gt;The next test was how well I could navigate Swagger UI with a keyboard. From this quick testing, it seems that Swagger UI is working somewhat ok with a keyboard. I could reach every element and interact with them. &lt;/p&gt;
&lt;h2 id=&quot;read-the-docs&quot;&gt;Read the Docs&lt;/h2&gt;
&lt;p&gt;URL used for testing: &lt;a href=&quot;https://docs.readthedocs.io/en/stable/tutorial/index.html&quot;&gt;https://docs.readthedocs.io/en/stable/tutorial/index.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Read the Docs is an open-source documentation platform that was created in 2010. On their website, they say they host over 800 000 open source projects and support over 100 000 users. &lt;/p&gt;
&lt;h3 id=&quot;testing-read-the-docs-with-axe&quot;&gt;Testing Read the Docs with Axe&lt;/h3&gt;
&lt;p&gt;Running Axe-browser extension showed 206 issues, 6 critical, 132 serious, and 3 moderate. For Read the Docs, too, the color contrast was the most significant cause of problems. The contrast ratio for the problematic color combinations ranged between 2.36 and 4.19.&lt;/p&gt;
&lt;p&gt;Read the Docs uses ARIA-attributes - but not correctly. For example, some headings are annotated as headings with ARIA but without the level of the heading. Here&amp;#39;s an example code from the site:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;p class=&amp;quot;caption&amp;quot; role=&amp;quot;heading&amp;quot;&amp;gt;
  &amp;lt;span class=&amp;quot;caption-text&amp;quot;&amp;gt;First steps&amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another problem this automated testing revealed is a scrollable area, which is not keyboard accessible. In this case, it&amp;#39;s a code-block visualizing warning messages. Lines are longer than the code block, adding horizontal scrolling to the element. And when a user, who uses a keyboard, comes to that particular element and wants to see what&amp;#39;s at the end of the line (hidden by scrolling), they can&amp;#39;t because that area is not keyboard accessible. &lt;/p&gt;
&lt;p&gt;Read the Docs is also missing landmarks, so the same problems as for Swagger UI are present. &lt;/p&gt;
&lt;h3 id=&quot;testing-read-the-docs-with-keyboard&quot;&gt;Testing Read the Docs with Keyboard&lt;/h3&gt;
&lt;p&gt;The search field is one of the first focusable items when navigating the page. When the user focuses on it, a modal opens. The &amp;quot;Close&amp;quot;-&amp;quot;button&amp;quot; is actually made of a &lt;code&gt;div&lt;/code&gt; without a possibility to focus on it. Fortunately pressing &lt;kbd&gt;Esc&lt;/kbd&gt; closes the modal.&lt;/p&gt;
&lt;p&gt;When the modal is visible, the focus is set to the modal&amp;#39;s first focusable element - the search bar. However, the focus is not trapped inside the modal, it&amp;#39;s only moved there, and the modal is the last item on the DOM. The user can still reach the underlying links and other focusable elements if they navigate backward (which is definitely not optimal).&lt;/p&gt;
&lt;p&gt;After closing the search modal, the focus is not managed. At least on my tests on Mac, Chrome, and Firefox, if I close the modal and then press &lt;kbd&gt;Tab&lt;/kbd&gt;, the focus ends up in the first tab of my browser. &lt;/p&gt;
&lt;p&gt;Combined with the on-focus behavior of the modal, this creates a sort of focus trap - the user can&amp;#39;t reach the rest of the page by tabbing forward. Yes, they can do that by navigating backward with &lt;kbd&gt;Shift&lt;/kbd&gt; and &lt;kbd&gt;Tab&lt;/kbd&gt;, but that&amp;#39;s problematic. And if the user can survive without the information on the page, they probably leave instead of trying to find all the ways they could navigate the site.&lt;/p&gt;
&lt;p&gt;Another thing I wanted to mention from the keyboard navigation point of view is that there&amp;#39;s no &amp;quot;Skip to content&amp;quot;-link. If the user somehow gets past the search modal, they need to tab through all of the links in the left side panel to reach the main content. On the example page, that means a lot of links to tab through. &lt;/p&gt;
&lt;h2 id=&quot;docusaurus&quot;&gt;Docusaurus&lt;/h2&gt;
&lt;p&gt;URLs used for testing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docusaurus.io/docs&quot;&gt;https://docusaurus.io/docs&lt;/a&gt; and &lt;a href=&quot;https://docusaurus.io/docs/styling-layout&quot;&gt;https://docusaurus.io/docs/styling-layout&lt;/a&gt; for automated testing&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docusaurus.io/docs/styling-layout&quot;&gt;https://docusaurus.io/docs/styling-layout&lt;/a&gt; for other tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Docusaurus is a static site generator created with React. On the Introduction page, they say that &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt; It provides out-of-the-box documentation features but can be used to create any kind of site (personal website, product, blog, marketing landing pages, etc.).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The team behind the Docusaurus is from Meta, as it&amp;#39;s one of its Open Source projects. &lt;/p&gt;
&lt;p&gt;I&amp;#39;m testing with Docusaurus version 2. At the time of testing, it was still in beta. However, now it&amp;#39;s published. I chose two pages to test so that I&amp;#39;d get a bit more components to experiment with. &lt;/p&gt;
&lt;h3 id=&quot;testing-docusaurus-with-axe&quot;&gt;Testing Docusaurus with Axe&lt;/h3&gt;
&lt;p&gt;Testing Docusaurus with Axe revealed 51 issues on the &amp;quot;Styling and Layout&amp;quot; page and 15 on the &amp;quot;Introduction&amp;quot;-page. From the first, 8 were &amp;quot;Needs review&amp;quot;, 1 critical, 41 serious, and 1 moderate. On the &amp;quot;Introduction&amp;quot; page, 14 needed review, and 1 was moderate.&lt;/p&gt;
&lt;p&gt;Most of the issues are about color contrast - on the &amp;quot;Styling and Layout&amp;quot;-page, 46 of the issues were about that. And some of the problems were because of examples of failing color contrast, so that&amp;#39;s expected. Others were mostly from the code snippets&amp;#39; styles.&lt;/p&gt;
&lt;p&gt;Other problems consisted of missing labels on a color input and non-unique landmarks.  &lt;/p&gt;
&lt;h3 id=&quot;testing-docusaurus-with-keyboard&quot;&gt;Testing Docusaurus with Keyboard&lt;/h3&gt;
&lt;p&gt;Most of the problems I encountered when testing Docusaurus with the keyboard were related to focus management. For example, when I opened the search modal and left it without typing anything on the search field, the focus was not managed and ended up at the beginning of the page.&lt;/p&gt;
&lt;p&gt;Another possible problem (although not a WCAG violation) is that Docusaurus uses the browser&amp;#39;s default focus styles. In some cases, the focus is hard to see, especially if the theme of the documents is close to the default focus indicator&amp;#39;s color.&lt;/p&gt;
&lt;p&gt;One thing I noticed is the menu&amp;#39;s dropdown-pattern Docusaurus uses. You can navigate the items on the dropdowns with &lt;kbd&gt;Tab&lt;/kbd&gt;, but that&amp;#39;s not the correct pattern for dropdowns in the menubar. Per &lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns/menu/&quot;&gt;ARIA Authoring Practices&lt;/a&gt;, the correct pattern would be that navigation happens with up and down arrows, and &lt;kbd&gt;Tab&lt;/kbd&gt; gets you to the next widget.&lt;/p&gt;
&lt;p&gt;I also found good things: Docusaurus has a &amp;quot;Skip to main content&amp;quot; link. It works - this is not always the case with skip-to-content links. Another nice thing is that the color picker in the &amp;quot;Styling and layout&amp;quot;-page works with a keyboard.&lt;/p&gt;
&lt;h2 id=&quot;gitbook&quot;&gt;GitBook&lt;/h2&gt;
&lt;p&gt;URL used for testing: &lt;a href=&quot;https://docs.gitbook.com/editing-content/rich-content/with-command-palette&quot;&gt;https://docs.gitbook.com/editing-content/rich-content/with-command-palette&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitBook is a documentation platform with lots of different features. It started as a simple open-source tool and grew into what it is today. It&amp;#39;s free for open-source projects and priced for everyone else.&lt;/p&gt;
&lt;h3 id=&quot;testing-gitbook-with-axe&quot;&gt;Testing GitBook with Axe&lt;/h3&gt;
&lt;p&gt;When I ran aXe on GitBook, there were 237 issues, from which 27 needed review, 2 were critical, 65 serious, and 143 moderate. &lt;/p&gt;
&lt;p&gt;There were many color contrast issues (61), and the non-passing elements had contrast ratios between 2,53 and 4.48. Also, there were problems with HTML semantics; &lt;code&gt;aria-label&lt;/code&gt;s on &lt;code&gt;div&lt;/code&gt;s, lists with incorrect markup,  and &lt;code&gt;lang&lt;/code&gt;-attribute missing. &lt;/p&gt;
&lt;p&gt;It also had missing landmarks and some missing alt-texts. And it had zooming disabled; this means that if someone needs to zoom in to see better, they can&amp;#39;t. It&amp;#39;s a terrible practice, making the site unusable for some users. &lt;/p&gt;
&lt;h3 id=&quot;testing-gitbook-with-keyboard&quot;&gt;Testing GitBook with Keyboard&lt;/h3&gt;
&lt;p&gt;Continuing with the theme &amp;quot;Unusable for some users&amp;quot;, GitBook doesn&amp;#39;t have a focus indicator for most elements, so it&amp;#39;s frustrating to try navigating with a keyboard. Because of that, I couldn&amp;#39;t test the site much with a keyboard.&lt;/p&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;In this blog post, I&amp;#39;ve shared results from quick tests I&amp;#39;ve made on four documentation tools: Swagger UI, Read the Docs, Docusaurus, and GitBook. Each has accessibility problems, but I would definitely pick Docusaurus for documentation from these four. &lt;/p&gt;
&lt;p&gt;After testing all the tools, I researched how they have reacted to accessibility. I wanted to know if they have accessibility statements on their pages, how they track accessibility issues, etc.&lt;/p&gt;
&lt;p&gt;Research on Swagger led me to Github. &lt;a href=&quot;https://github.com/swagger-api/swagger-ui/issues?q=is%3Aopen+is%3Aissue+label%3A%22cat%3A+a11y%22&quot;&gt;Swagger tracks the accessibility issues with a &lt;code&gt;cat: a11y&lt;/code&gt;-tag&lt;/a&gt;, but there seems to have been no activity this year. Search with &amp;quot;accessibility&amp;quot; on Swagger&amp;#39;s site did not find anything, so I&amp;#39;m assuming they don&amp;#39;t have any statements. &lt;/p&gt;
&lt;p&gt;Read the Docs has no policies or statements on accessibility. They have some Github issues when searching for accessibility in the theme&amp;#39;s repository. Read the Docs has a theming system, so in theory, the user can fix some of the problems found with the search. And with &amp;quot;some&amp;quot;, I mean the ones related to styles (so, color, for example). If I&amp;#39;ve understood correctly, these themes are about styles, not the underlying HTML.&lt;/p&gt;
&lt;p&gt;In Docusaurus&amp;#39; introduction, they say that  Docusaurus has &amp;quot;attention to accessibility, making your site equally accessible to all users.&amp;quot; They also track accessibility issues with the &lt;code&gt;domain: a11y&lt;/code&gt;-tag in Github. However, I couldn&amp;#39;t find any separate accessibility statement for Docusaurus.&lt;/p&gt;
&lt;p&gt;Last, Gitbook mentions accessibility only in &amp;quot;Space customisation&amp;quot;&amp;#39;s &amp;quot;Primary color&amp;quot; section, where there is a mention about accessibility being important when selecting colors. When searching for accessibility and Gitbook, I found a couple of pages using Gitbook and writing about accessibility. That&amp;#39;s a bit ironic - as the Gitbook itself is not that accessible.&lt;/p&gt;
&lt;p&gt;What&amp;#39;s your favorite documentation tool? Have you thought about or tested its accessibility? &lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-07-07/how-can-backend-developers-improve-accessibility/&quot;&gt;blog post about what backend devs can do for accessibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://petstore3.swagger.io/&quot;&gt;https://petstore3.swagger.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.readthedocs.io/en/stable/tutorial/index.html&quot;&gt;https://docs.readthedocs.io/en/stable/tutorial/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docusaurus.io/docs&quot;&gt;https://docusaurus.io/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docusaurus.io/docs/styling-layout&quot;&gt;https://docusaurus.io/docs/styling-layout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns/menu/&quot;&gt;ARIA Authoring Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.gitbook.com/editing-content/rich-content/with-command-palette&quot;&gt;https://docs.gitbook.com/editing-content/rich-content/with-command-palette&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/swagger-api/swagger-ui/issues?q=is%3Aopen+is%3Aissue+label%3A%22cat%3A+a11y%22&quot;&gt;Swagger tracks the accessibility issues with a &lt;code&gt;cat: a11y&lt;/code&gt;-tag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Notes of a Web Developer Learning Kotlin and Android Development - part 2</title>
    <link href="https://eevis.codes/blog/2022-08-18/notes-of-a-web-developer-learning-kotlin-and-android-development-part-2/" />
    <updated>2023-01-03T08:57:39.139Z</updated>
    <id>https://eevis.codes/blog/2022-08-18/notes-of-a-web-developer-learning-kotlin-and-android-development-part-2/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/7j78KHLnSoeXQlZx0gQDwb/c343b3d98b6446d45a30fab65200c972/Kotlin-Android-part2on-the-edge-of-burnout.png"/>]]>
      &lt;p&gt;&lt;em&gt;This blog post is the second in a series of posts about me working through the &lt;a href=&quot;https://developer.android.com/courses/android-basics-kotlin/course&quot;&gt;Android Basics on Kotlin-course&lt;/a&gt;. You can find the first one here: &lt;a href=&quot;https://eevis.codes/blog/2022-08-02/notes-of-a-web-developer-learning-kotlin-and-android-development-part-1/&quot;&gt;Notes of a Web Developer Learning Kotlin and Android Development - part 1&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The second unit of the course, &amp;quot;Android Basics in Kotlin,&amp;quot; is about Layouts. It also goes through some Kotlin-concepts, such as lists and &lt;code&gt;with&lt;/code&gt;-syntax. From the Android side, the most significant theme is layouts, particularly adapters.&lt;/p&gt;
&lt;h2 id=&quot;kotlin-lists-and-other-things&quot;&gt;Kotlin: Lists and Other Things&lt;/h2&gt;
&lt;h3 id=&quot;lists&quot;&gt;Lists&lt;/h3&gt;
&lt;p&gt;There are different list types in Kotlin. Lists can be either mutable or immutable - you need to decide which one when defining the list. That&amp;#39;s different from JavaScript: Even though you define them as constant in JS, you can still add and remove things. In Kotlin, you need to have a mutable list to do that. &lt;/p&gt;
&lt;p&gt;Lists are defined with the following syntaxes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val immutableList = listOf(&amp;quot;list&amp;quot;, &amp;quot;of&amp;quot;, &amp;quot;words&amp;quot;)
val mutableList = mutableListOf(&amp;quot;other&amp;quot;, &amp;quot;words&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Printing these would result in&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;[list, of, words]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;[other, words]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can get the first item from a list with method &lt;code&gt;first()&lt;/code&gt; and the last item with method &lt;code&gt;last()&lt;/code&gt;. Sorting a list happens with the &lt;code&gt;sorted()&lt;/code&gt;-method, and you can reverse a list with &lt;code&gt;reversed()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Mutable lists have a set of additional methods to mutate the list; here are some examples:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;code&gt;add()&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;Add an item to the list.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;addAll()&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;Add a collection to the list. More about other collection types in the next part.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;remove()&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;Remove an item from the list.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;clear()&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;Clear the list.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;isEmpty()&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;Check if the list is empty.&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;Again, here&amp;#39;s plenty more than what you&amp;#39;ll have with JavaScript. I mean, you can do all of these with JS, but you need to write more code for that, and it&amp;#39;s not as verbose. Let&amp;#39;s take a look at checking if a list is empty. With Kotlin, it would be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// Kotlin

val list = listOf(...)
val isListEmpty = list.isEmpty()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// JavaScript

const list = [...]
const isListEmpty = list.length === 0
// or
const isListEmptyShorter = !list.length
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A final thing about the lists in this post is looping through them. The material mentions two methods - &lt;code&gt;while&lt;/code&gt; and &lt;code&gt;for&lt;/code&gt;-loops. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;while&lt;/code&gt;-loops go on, as long its condition is satisfied. It first checks the condition and executes the body if it&amp;#39;s true.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;while (x &amp;lt; 10) {
    // Do something
    ++x
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For-loops iterates through the collection provided. So, for example, looping through the range of numbers could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;for (i in 1..6) {
   // Do something with the current number
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;with&quot;&gt;&lt;code&gt;with&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;with&lt;/code&gt;-syntax is nice when working with, for example, a specific class instance, and there&amp;#39;s a need to access more than one of its properties and functions. &lt;/p&gt;
&lt;p&gt;So, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;with(cat) {
    println(&amp;quot;Cat&amp;#39;s name is ${name}&amp;quot;)
    println(&amp;quot;It often sleeps in ${favoritePlace}&amp;quot;)
    meow()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could access these properties with &lt;code&gt;cat.name&lt;/code&gt; or &lt;code&gt;cat.meow()&lt;/code&gt;, but I need to write less code this way.&lt;/p&gt;
&lt;h2 id=&quot;android-lets-talk-about-adapters&quot;&gt;Android: Let&amp;#39;s Talk About Adapters&lt;/h2&gt;
&lt;p&gt;Adapters are the most significant learning in this unit - or refresher, as I recall some distant things about them. They work as, you guessed, adapters between views and underlying data. &lt;/p&gt;
&lt;p&gt;The adapter is a design pattern. It adapts the data and transforms it into something the client can use. In the course, the example is about &lt;code&gt;RecyclerView&lt;/code&gt;, and how the data is adapted to it.&lt;/p&gt;
&lt;p&gt;When introducing the adapters, the app being built is a list of affirmations. &lt;code&gt;RecyclerView&lt;/code&gt; is suitable for this project as it&amp;#39;s a good component for creating dynamic lists efficiently. &lt;/p&gt;
&lt;p&gt;The course explains how an adapter works with &lt;code&gt;RecyclerView&lt;/code&gt; in the app with the following words:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you run the app, RecyclerView uses the adapter to figure out how to display your data on screen. RecyclerView asks the adapter to create a new list item view for the first data item in your list. Once it has the view, it asks the adapter to provide the data to draw the item. This process repeats until the RecyclerView doesn&amp;#39;t need any more views to fill the screen. If only 3 list item views fit on the screen at once, the RecyclerView only asks the adapter to prepare those 3 list item views (instead of all 10 list item views).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the app, the adapter has multiple parts. First, a layout is created for an individual list item. The next step is to create an &lt;code&gt;ItemAdapter&lt;/code&gt;-class, which takes in a list of affirmations, and context-object instance. &lt;/p&gt;
&lt;p&gt;After that, an &lt;code&gt;ItemViewHolder&lt;/code&gt; is created inside the &lt;code&gt;ItemAdapter&lt;/code&gt;. It represents a single list item view in the &lt;code&gt;RecyclerView&lt;/code&gt; and can be reused. That view holder is the one that handles updating and showing data. &lt;/p&gt;
&lt;p&gt;The adapter needs to implement three methods: &lt;code&gt;onCreateViewHolder&lt;/code&gt;, &lt;code&gt;getItemCount&lt;/code&gt; and &lt;code&gt;onBindViewHolder&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;The layout manager calls &lt;code&gt;onCreateViewHolder&lt;/code&gt; to create new view holders for the &lt;code&gt;RecyclerView&lt;/code&gt;, if there are no existing view holders that it could reuse.  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;getItemCount&lt;/code&gt; is for, as the name suggests, getting the item count for the list data. The layout manager calls &lt;code&gt;onBindViewHolder&lt;/code&gt; to replace the contents of a list item view.&lt;/p&gt;
&lt;p&gt;There&amp;#39;s one more step: Once the adapter is created, &lt;code&gt;RecyclerView&lt;/code&gt; needs to know about it. In the course project, &lt;code&gt;MainActivity&lt;/code&gt; is the place to do that. To be more specific, in the &lt;code&gt;onCreate&lt;/code&gt;-method of that activity.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve had a hard time wrapping my head around the pattern. However, I think it&amp;#39;s partly because it&amp;#39;s a new, complex thing, and to be honest, it&amp;#39;s been a while since the last last time I did object-oriented programming.&lt;/p&gt;
&lt;p&gt;So I need to keep practicing - this concept among all the others. Luckily, from what I&amp;#39;ve understood, this is something I&amp;#39;ll need many times when learning more about Android development.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;There were many things to learn in unit 2 of the Android Basics in Kotlin-course. I know I will return to these topics in the following units - and definitely when I start creating something of my own. &lt;/p&gt;
&lt;p&gt;The next unit is about navigation, and that&amp;#39;s an interesting one. I&amp;#39;ve already started it, and there&amp;#39;s so much to know! I&amp;#39;m so excited about these new concepts I&amp;#39;m learning. As there is so much material and new things in the Navigation unit, I might split it into two posts. We&amp;#39;ll see.&lt;/p&gt;
&lt;p&gt;Have you been learning Kotlin or Android development? Have any tips or thoughts?&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/courses/android-basics-kotlin/course&quot;&gt;Android Basics on Kotlin-course&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-08-02/notes-of-a-web-developer-learning-kotlin-and-android-development-part-1/&quot;&gt;Notes of a Web Developer Learning Kotlin and Android Development - part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Learnings From Creating a Guest Book App</title>
    <link href="https://eevis.codes/blog/2022-08-24/learnings-from-creating-a-guest-book-app/" />
    <updated>2023-01-03T08:57:38.780Z</updated>
    <id>https://eevis.codes/blog/2022-08-24/learnings-from-creating-a-guest-book-app/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1JQwvh0lyHuXULSqSF7Q6A/b602b7d1bc857592fddcc8783be2bdf5/Kotlin-Android-part2on-the-edge-of-burnout__1_.png"/>]]>
      &lt;p&gt;So it seems my sister is currently my biggest muse for side projects. She was the reason I created &lt;a href=&quot;https://neule.art/en/&quot;&gt;Neule.art&lt;/a&gt;, from which you can read more in &lt;a href=&quot;https://eevis.codes/blog/2022-06-30/how-i-created-neule-art/&quot;&gt;the blog post &amp;quot;How I created Neule.art&amp;quot;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A while back, she asked me if I could create a guest book app for a party they had with their friends. The idea was simple - there should be a possibility to add a photo, some text, and the name(s) of the sender(s). They tried to search for a readymade app, but every one of them had some problems.&lt;/p&gt;
&lt;p&gt;I also know, from experience, that these kinds of apps can be... how to put this... Not so good usability-wise. For example, no one wants to download an app for one night to be able to take one or two pictures. No one wants to create an account for that one night (and then forget that they have it). &lt;/p&gt;
&lt;p&gt;So I wanted to try out if I could build a simple enough, non-account-needing web app. In this blog post, I won&amp;#39;t share the code for that app. It was a bit hacky, as the app needed to work only for that one night. Aaand I was a bit in a hurry. However, I will share some learnings from creating that app. Let&amp;#39;s first have a look at what the app was like.&lt;/p&gt;
&lt;h2 id=&quot;the-guest-book-app&quot;&gt;The Guest Book App&lt;/h2&gt;
&lt;p&gt;The guest book app consisted of two pages: Login, which had this one input field for writing the password, and a page for guest book entries. In addition, there was a modal for adding a new entry. That had a form with the possibility of taking a photo (a file-type input field with &lt;code&gt;accept=&amp;quot;image/*&amp;quot;&lt;/code&gt; to open the camera) and adding names and a message.&lt;/p&gt;
&lt;p&gt;As for the tech stack, I used NextJS with TypeScript, GraphQL, and GraphCMS (at the time, they changed their name just after I finished the project to Hygraph) for CMS. As for the photos, I stored them in an AWS S3 bucket and their URL to the CMS.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve worked with all the other technologies before, but AWS was something new. However, I was pretty sure that because it&amp;#39;s a fairly common use case to store things in AWS S3 buckets, and as React is so popular, there must be an easy solution for uploading files to S3 buckets from React apps. Turns out there is - but it wasn&amp;#39;t that easy to upload the photos. That leads us to the first lesson learned. &lt;/p&gt;
&lt;h2 id=&quot;theres-always-a-typo-somewhere&quot;&gt;There&amp;#39;s Always a Typo Somewhere&lt;/h2&gt;
&lt;p&gt;As mentioned, this was my first time working with AWS. But I&amp;#39;ve heard the stories (mainly about someone forgetting to turn off something and getting an astronomical bill when they realize it the following day) - and I was prepared for problems. &lt;/p&gt;
&lt;p&gt;I followed some tutorials on setting up an AWS S3 bucket, and to my surprise, that was easy. The problems started when I tried to upload the photo to the bucket. I tried a couple of packages and had CORS issues I couldn&amp;#39;t solve. &lt;/p&gt;
&lt;p&gt;I didn&amp;#39;t understand why, and I was already ready to quit software development after spending a couple of days banging my head on the wall because of this issue. But I finally solved it.&lt;/p&gt;
&lt;p&gt;The cause? Typo in the bucket name in the code.&lt;/p&gt;
&lt;h2 id=&quot;netlify-nextjs-images-and-orientation&quot;&gt;Netlify, NextJS Images, and Orientation&lt;/h2&gt;
&lt;p&gt;Another issue I had (way more minor than the CORS issues) was with the NextJS Image component and Netlify. I had planned to use Netlify for hosting, and I was almost finished. I deployed the app and sent it for testing. &lt;/p&gt;
&lt;p&gt;It turned out that Netlify removed the EXIF data from the images for some reason. Some of the photos were weirdly rotated. I found some discussions where (if I remember correctly) the team confirmed that this is what can happen. While writing this blog post, I searched for the issues/forum posts/anything I&amp;#39;ve read, but I couldn&amp;#39;t find anything. So I can&amp;#39;t back this up.&lt;/p&gt;
&lt;p&gt;I would have loved to solve this problem in some elegant way. However, at that point, I didn&amp;#39;t have too much time to finish the app. So I decided to use Vercel for hosting - NextJS images worked well with it as it&amp;#39;s the company behind NextJS. &lt;/p&gt;
&lt;h2 id=&quot;simple-things-go-a-long-way&quot;&gt;Simple Things Go a Long Way&lt;/h2&gt;
&lt;p&gt;Sometimes, we need to remind ourselves that simple things go a long way. When we build apps and sites, it doesn&amp;#39;t always have to be something super complicated and have a multitude of features. &lt;/p&gt;
&lt;p&gt;As I mentioned initially, this app aimed to be as simple as possible. It was an answer to my (and others&amp;#39;) frustrations with the available guest book apps that either required app installation or an account (or both). And it delivered what it was supposed to: People could leave guest book entries for the hosts. &lt;/p&gt;
&lt;p&gt;I&amp;#39;m not saying this app was distribution-ready, but it would have been a good starting point had I had time to make it more configurable. Or to open source it, because right now, the codebase looks like I hacked it together in a couple of days. That&amp;#39;s precisely what I did. &lt;/p&gt;
&lt;p&gt;I try to remind my mentees and anyone seeking my advice that it&amp;#39;s better to have a simple app completed than a bigger one in progress when applying for jobs. The simple app can always have improvements and next steps, but I try to stress that it&amp;#39;s good to have the MVP finished. &lt;/p&gt;
&lt;h2 id=&quot;stepping-out-from-the-tech-bubble-does-you-good&quot;&gt;Stepping Out From the Tech Bubble Does You Good&lt;/h2&gt;
&lt;p&gt;The fourth thing I want to talk about is that if you usually spend time with developers (like I do), try sometimes showing your projects to someone outside that bubble. It can really boost your confidence.&lt;/p&gt;
&lt;p&gt;When I went to the party and spoke with one of the hosts, it helped me to remember that what I can do is a lot. If I&amp;#39;m showing my projects to anyone who knows about coding, I think a lot about what could be better and the improvements. Case in point: I don&amp;#39;t want to share the code with you, my reader. &lt;/p&gt;
&lt;p&gt;Another person who I can rely upon to remind me about these points is my sister. I love her enthusiasm when I show her something I&amp;#39;ve coded. It&amp;#39;s been like that ever since I started to learn to code, and I appreciate her for that  &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;It was fun to create a project for someone&amp;#39;s use. Even though there were some problems to solve, I&amp;#39;m proud of myself. I&amp;#39;m proud that I made this app, and it was usable. I&amp;#39;m also proud that I solved those problems. &lt;/p&gt;
&lt;p&gt;Do you have any apps/sites in progress? Or have you finished something lately? &lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://neule.art/en/&quot;&gt;Neule.art&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-06-30/how-i-created-neule-art/&quot;&gt;The blog post &amp;quot;How I created Neule.art&amp;quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/EevisPanula/status/1545704858472292352&quot;&gt;Link to the tweet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Hey Tech Recruiter, Here Are Some Tips from a Developer</title>
    <link href="https://eevis.codes/blog/2022-11-06/hey-tech-recruiter-here-are-some-tips-from-a-developer/" />
    <updated>2023-01-03T08:57:36.505Z</updated>
    <id>https://eevis.codes/blog/2022-11-06/hey-tech-recruiter-here-are-some-tips-from-a-developer/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/7vTlp9zRHEBA0bGrZDHkG7/19f47697fc50ac125486a5442a31f3c7/tech-recruiteron-the-edge-of-burnout.png"/>]]>
      &lt;p&gt;I wrote a rant to LinkedIn in the summer, summarizing a couple of tips on how (not) to contact me and similar stuff. Since then, I&amp;#39;ve been thinking about writing a blog post about this topic, and here it finally is!&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve had good experiences with tech recruiters over the past years, but unfortunately, there have also been bad experiences. And this blog post comes from those occasions.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s the original rant: &lt;/p&gt;
&lt;blockquote cite=&quot;https://www.linkedin.com/posts/eevajonna_dear-tech-recruiter-here-are-a-couple-of-activity-6953278988425330688-pN9y&quot;&gt;
&lt;p&gt;Dear tech recruiter, here are a couple of things I&#39;d wish from you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✨ Please don&#39;t call me. Even though you can find my number somewhere, it&#39;s not an open invitation to call. I hate speaking on the phone, so you calling me unsolicitedly makes me anxious. And I don&#39;t want to work for your company or your client if you make me anxious.&lt;/li&gt;
&lt;li&gt;✨ Please don&#39;t call me many times. You know, the seventh call within three days is just too much. (The first one was too, but… seventh. C&#39;moon.)&lt;/li&gt;
&lt;li&gt;✨ If you say you&#39;re a tech recruiter, you really should know the difference between frontend, backend, and devops. And you should look at my profile at least just enough to know I&#39;m a frontend developer - and not contact me with backend / devops-positions.&lt;/li&gt;
&lt;li&gt;✨ Oh, and a tip: Java and JavaScript are different languages. Me having JavaScript on my profile does not mean I have extensive knowledge on Java.&lt;/li&gt;
&lt;li&gt;✨ I won&#39;t answer &quot;Hey I have this mysterious client you would be lucky to work for&quot;-type of messages. Sorry, I know you always can&#39;t disclose the client, but I want to know where I&#39;m getting into if I take a call with someone.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;And a ✨Bonus Tip✨: Oh, and if your company&#39;s/your client&#39;s website has these fancy animations/auto-playing things that make me literally sick I don&#39;t want to work for you or your client. I know I&#39;m an accessibility specialist who could come and fix these things, but from the messages I&#39;ve received, you&#39;re not looking for that. You&#39;re looking for a backend developer. So… no.&lt;/p&gt;

&lt;p&gt;Yes, it&#39;s been one of those weeks.&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/posts/eevajonna_dear-tech-recruiter-here-are-a-couple-of-activity-6953278988425330688-pN9y&quot;&gt;Source: LinkedIn&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These tips are written from my perspective, and someone might disagree with them. But from what I&amp;#39;ve discussed with several other developers,  they have agreed with me on these tips.&lt;/p&gt;
&lt;p&gt;If you find yourself or your company from these examples, I don&amp;#39;t assume you&amp;#39;re doing it because you want to be evil or something like that. I don&amp;#39;t think you&amp;#39;re a terrible person because of that. I just wanted to say that. And now we can get to the tips.&lt;/p&gt;
&lt;h2 id=&quot;dont-call-me&quot;&gt;Don&amp;#39;t Call Me&lt;/h2&gt;
&lt;p&gt;Really, unless we have agreed in a conversation (which was in written form) that it&amp;#39;s okay that you call me, don&amp;#39;t. I don&amp;#39;t like speaking with people I don&amp;#39;t know on the phone. Also, due to some issues related to memory, I&amp;#39;d rather have everything written than try to remember details like who that person was, what the company was, etc.&lt;/p&gt;
&lt;p&gt;And definitely don&amp;#39;t call me multiple times. I won&amp;#39;t answer you. I use search engines to find the number, and after I see that the number belongs to a potential recruiter, I write &amp;quot;Don&amp;#39;t answer&amp;quot; as a note to it. And you know, seven calls within three work days is just way too much. &lt;/p&gt;
&lt;h2 id=&quot;know-the-basics-of-tech&quot;&gt;Know The Basics of Tech&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;re a tech recruiter, you should know the basics of tech. I don&amp;#39;t mean that you should learn how to code - even though it is fun! No, you should know the difference between frontend, backend, DevOps, mobile, and other similar concepts. You should also have a basic understanding of programming languages at a level that you know that Java is different from JavaScript.&lt;/p&gt;
&lt;p&gt;I get these messages fairly often that say, &amp;quot;Hey! We are/our client is looking for someone with extensive knowledge on Java to work with backend.&amp;quot; Did you check what my LinkedIn profile says about my skills? I mean, it talks about frontend technologies, accessibility, and JavaScript. Not Java. There&amp;#39;s no Java anywhere. &lt;/p&gt;
&lt;p&gt;And I know that that is a tactic just to send many messages out there and hope someone answers. However, for every message I receive that&amp;#39;s like that, I add the recruiter and company to my mental list. I don&amp;#39;t want to work with companies that don&amp;#39;t have a basic understanding of tech because it already tells me a lot about the culture.&lt;/p&gt;
&lt;h2 id=&quot;give-me-all-the-details&quot;&gt;Give Me All the Details&lt;/h2&gt;
&lt;p&gt;I receive many &amp;quot;I have this mysterious client in this and that sector looking for someone&amp;quot;-messages. Sometimes I might even bother answering and say that I&amp;#39;m not interested in starting a recruitment process with a company I know very little about. &lt;/p&gt;
&lt;p&gt;And I understand that it&amp;#39;s not always possible to reveal the name of the company, but I&amp;#39;d like to question the whole concept - why it needs to be like that? Having as much information as possible is best for both parties.&lt;/p&gt;
&lt;p&gt;Knowing what I&amp;#39;m getting into (even if it&amp;#39;s only the first interview or a call with a recruiter) is essential. It&amp;#39;s a stressful situation, and if I go in without almost any information, I can&amp;#39;t prepare. And I don&amp;#39;t like that feeling. I need time to process my answers, and if I don&amp;#39;t have any time to think about them before the interview, I will fail, and it&amp;#39;s a waste of time for both parties.&lt;/p&gt;
&lt;h2 id=&quot;be-honest-about-the-process&quot;&gt;Be Honest About the Process&lt;/h2&gt;
&lt;p&gt;Over the years, I&amp;#39;ve had several experiences where I agreed to have a meeting to &amp;quot;get to know each other and casually chat.&amp;quot; And when that meeting started, the first question was something like, &amp;quot;Why do you want to work at this company?&amp;quot; Like, hey, I don&amp;#39;t know if I want to! I&amp;#39;m here to find it out!&lt;/p&gt;
&lt;p&gt;And don&amp;#39;t get me even started on surprise technical interviews. If the information about the meeting is that we&amp;#39;ll casually talk about technical topics, a live coding exercise is not okay. And neither is being a dick about not doing that exercise. (Okay, that was not a recruiter.)&lt;/p&gt;
&lt;p&gt;The information and agenda of the meetings need to be known beforehand. I need to prepare for them, and if the agenda is something other than what was agreed upon, I can&amp;#39;t prepare. And I feel shitty about that. And if I feel shitty... I probably don&amp;#39;t want to work at that company. I am just saying.&lt;/p&gt;
&lt;h2 id=&quot;do-what-youve-promised---and-communicate-if-you-cant&quot;&gt;Do What You&amp;#39;ve Promised - And Communicate If You Can&amp;#39;t&lt;/h2&gt;
&lt;p&gt;When a tech recruiter says they&amp;#39;ll contact me at a certain time, I assume they&amp;#39;ll do that. But I can&amp;#39;t even count the times when I&amp;#39;ve been told that a company would get back to me at a certain date, and there was nothing. Just silence. And when I asked about that, they either ghosted me or treated me as if I should be thankful that they even answered.&lt;/p&gt;
&lt;p&gt;It&amp;#39;s okay if some things take time. Sometimes people deciding about the next steps are unavailable, and that&amp;#39;s life. Sending a short message like &amp;quot;Hey, I know we promised to get back to you today, but I am sorry, [insert reason for the delay], and we&amp;#39;ll get back to you [insert new timeline].&amp;quot; shouldn&amp;#39;t be too much effort. &lt;/p&gt;
&lt;p&gt;And yes, things happen. And it&amp;#39;s okay - if you communicate about it. Sending that short message mentioned in the previous paragraph shows respect toward the candidate.&lt;/p&gt;
&lt;h2 id=&quot;treat-juniors-and-people-from-minorities-in-tech-with-respect&quot;&gt;Treat Juniors and People From Minorities in Tech with Respect&lt;/h2&gt;
&lt;p&gt;I know we are in an era where everyone wants a senior developer, and there are just not enough of us. And there&amp;#39;s the fact that white men are often seen as more competent than others. But if you only treat the ones you would like to hire now with respect and not, e.g., ghost them, it will bite you back.&lt;/p&gt;
&lt;p&gt;You know, those juniors will be seniors one day. And those people coming from minorities in tech are as competent as, well, white men. And as the tech scene is small, we also often tell about our experiences to others - seniors as well. &lt;/p&gt;
&lt;p&gt;I have many examples where my being treated poorly led to someone more senior than me refusing even an interview with a company because of my experiences. And as I&amp;#39;ve become more senior, I&amp;#39;ve also refused to interview because of those experiences. &lt;/p&gt;
&lt;p&gt;For many, this is a no-brainer. But from my experience, there are a lot of companies and/or recruiters who don&amp;#39;t get that.&lt;/p&gt;
&lt;h2 id=&quot;check-your-websites-accessibility&quot;&gt;Check Your Website&amp;#39;s Accessibility&lt;/h2&gt;
&lt;p&gt;On Global Accessibility Awareness Day 2021, I got a call from a recruiter. They told me from which company they were, and I opened their website. I was on my computer and had a large screen with a browser in full-screen mode, so everything was big on the site. It contained some pretty wild animations, which triggered my symptoms. I felt very disoriented and tried to get away from the call (I&amp;#39;m too nice to hang up). &lt;/p&gt;
&lt;p&gt;I tried to keep it together just enough to explain what had happened - and honestly, I have no idea if I succeeded. After the call, I needed to lie down for over 30 minutes, and I couldn&amp;#39;t continue working for a while. &lt;/p&gt;
&lt;p&gt;That is my reality with all those fancy parallax scrollings and other effects. I need to be extra careful when going to a new site. And I&amp;#39;m not alone. &lt;/p&gt;
&lt;p&gt;Also, there are lots of disabled people who you might want to hire. But if your company&amp;#39;s website is not accessible, they either encounter similar things as I do, or they can&amp;#39;t use the website at all if it&amp;#39;s not accessible. And let me tell you - you probably won&amp;#39;t be able to hire us. &lt;/p&gt;
&lt;h2 id=&quot;check-your-websites-representation&quot;&gt;Check Your Website&amp;#39;s Representation&lt;/h2&gt;
&lt;p&gt;Looking at some companies&amp;#39; websites, the wanted employee consists of these attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Man&lt;/li&gt;
&lt;li&gt;Plays video games&lt;/li&gt;
&lt;li&gt;Drinks beer&lt;/li&gt;
&lt;li&gt;Has a technical master&amp;#39;s degree (and from a certain university)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And you know... I don&amp;#39;t fit into that. I&amp;#39;m a woman; I don&amp;#39;t play video games because I didn&amp;#39;t have the opportunity when I was a child. Nowadays, I get frustrated because everyone else is so much better at them. &lt;/p&gt;
&lt;p&gt;I don&amp;#39;t drink beer (I rarely drink alcohol at all), and I don&amp;#39;t have a technical master&amp;#39;s degree - after all, as a developer, I&amp;#39;m self-taught.&lt;/p&gt;
&lt;p&gt;And I don&amp;#39;t mean that if I don&amp;#39;t fit in, then nobody who&amp;#39;s from a minority wouldn&amp;#39;t. But only some people fit into that. Even if the actual culture inside the company is something different, we, the potential recruits, won&amp;#39;t know it. That is if the websites and job advertisements give a one-sided picture of the company and its culture.&lt;/p&gt;
&lt;p&gt;So, check the imagery of your company&amp;#39;s websites and advertisements. Does everyone in the pictures look the same? Or is there diversity in these images? &lt;/p&gt;
&lt;p&gt;Another thing to check about the imagery is that it doesn&amp;#39;t echo the idea of white men being the seniors and POC, women, and other minorities in tech as juniors/trainees. I can&amp;#39;t count how often I get targeted ads where senior roles have a man in the picture, and for the junior position, there&amp;#39;s a woman in the advertisement.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;This blog post might feel a bit sharp-tempered, and you&amp;#39;re right; that was the point. I wanted to write these feelings out. I am frustrated with certain types of messages (and calls) I get from recruiters.&lt;/p&gt;
&lt;p&gt;However, only some recruiters are like that. I&amp;#39;ve had many good convos with fantastic recruiters who clearly respect the (potential) candidate and their time. &lt;/p&gt;
&lt;p&gt;If you found yourself or your company from one of the points, here&amp;#39;s the good news: Now you can learn and do better in the future! It&amp;#39;s the best place to be - without learning, we never can get better. &lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Android, Animations and Reduced Motion</title>
    <link href="https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/" />
    <updated>2023-01-03T08:57:37.971Z</updated>
    <id>https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1xKIWhmB62VgG1aqZpsFVr/697f97cefb3de65c88a0635b66e8dfb0/android_and_reduced_motion.png"/>]]>
      &lt;p&gt;I don&amp;#39;t like animations, and I&amp;#39;m not alone. The Verge published an &lt;a href=&quot;https://www.theverge.com/23191768/animation-accessibility-neurodivergence&quot;&gt;article by s. e. smith: My war on animation&lt;/a&gt; as part of their Accessibility week, and I loved it. I was able to relate to that article on so many levels.  &lt;/p&gt;
&lt;p&gt;For me, animation and motion on a website or app can be a problem for two reasons: They might make me physically sick and distract me. Especially when I&amp;#39;m tired, distraction is a huge problem. &lt;/p&gt;
&lt;p&gt;So, now you might wonder, how can I survive the internet today? Animations are everywhere. &lt;/p&gt;
&lt;p&gt;There&amp;#39;s an app for that! Or, rather, a setting. I can turn motion off (or reduce it) with operating system settings on my phone and computer. When this setting is on, operating system level animations are turned off. &lt;/p&gt;
&lt;p&gt;How about websites and apps, then? Do they respect that setting? Yes and no. It entirely depends on the developers implementing the reduced motion-media query on the web. On the other hand, in apps, it depends on how the animations are built.&lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;m talking about Android animations and how to respect that setting. I also discuss when you need to implement the check and when the animations are turned off by default.&lt;/p&gt;
&lt;p&gt;But first, let&amp;#39;s talk a bit about why you should care about respecting that setting.&lt;/p&gt;
&lt;h2 id=&quot;why-is-this-important&quot;&gt;Why is this Important?&lt;/h2&gt;
&lt;p&gt;It&amp;#39;s the right thing to do. I mean, users have expectations on how apps work, and one of these expectations is that operating system-level settings are respected. &lt;/p&gt;
&lt;p&gt;Sometimes not respecting that setting creates inconveniences, but sometimes it&amp;#39;s about real, life-affecting symptoms that might get triggered. Nobody enjoys having a migraine or feeling sick after encountering something unexpected in the app - something that could have been prevented with a few lines of code. &lt;/p&gt;
&lt;p&gt;I know there&amp;#39;s not much knowledge about the need for reduced motion. I&amp;#39;ve worked with multiple devs, designers, and product people who&amp;#39;ve had no idea about that. And animations are here to stay - they drive engagement (or that&amp;#39;s what everyone keeps telling me), and some people like them. But I just hope &amp;quot;engagement&amp;quot; won&amp;#39;t drive over the accessibility needs of the people - so that&amp;#39;s why I&amp;#39;m trying to educate everyone about this topic. &lt;/p&gt;
&lt;h2 id=&quot;when-to-check-the-reduced-motion-setting-in-your-code&quot;&gt;When to Check the Reduced Motion Setting in Your Code?&lt;/h2&gt;
&lt;p&gt;Reduced motion (or, animations off) -setting affects all Animator-based animations. That means most of the animations you use are turned off by default. The other week, I was developing a feature that required me to turn that setting off because of the animations. I was surprised by how much motion and animation was being turned off by that setting!  &lt;/p&gt;
&lt;p&gt;Also, when creating animations with Jetpack Compose, its animation classes follow that setting from version 1.2.0. However, if you&amp;#39;re using earlier versions, I&amp;#39;d recommend testing if the setting is respected out of the box.&lt;/p&gt;
&lt;p&gt;Another case when you need to know how to check if the reduced motion setting is on is if you&amp;#39;re using something like Lottie-animations. I love how Lottie&amp;#39;s Android component checks for reduced motion and displays the first frame if it&amp;#39;s on - and all of this, out of the box. &lt;/p&gt;
&lt;p&gt;However, sometimes the first frame is not applicable. Often animations start with an (almost) empty screen, which is not what you want to show users with reduced motion. So in these cases, checking if reduced motion is on and displaying a static image instead is the way to go. &lt;/p&gt;
&lt;p&gt;So, there&amp;#39;s no need for extra checks in many cases - at least if you&amp;#39;re using the latest versions of, e.g., Compose. However, testing is the key. You can turn the reduced motion setting on from the accessibility settings - the setting is called &amp;quot;Remove animations,&amp;quot; and depending on your phone, it might be under &amp;quot;Display&amp;quot; or &amp;quot;Visibility enhancements&amp;quot; or other similar. &lt;/p&gt;
&lt;h2 id=&quot;how-to-detect-if-reduced-motion-is-enabled&quot;&gt;How to Detect if Reduced Motion is Enabled&lt;/h2&gt;
&lt;p&gt;Since Android doesn&amp;#39;t expose the value for reduced motion the same way as other accessibility services, such as if a screen reader is enabled, we need to be a little creative.&lt;/p&gt;
&lt;p&gt;One way to deduce if reduced motion is enabled is to check the animation duration scale. We can get that value with &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Settings.Global.getFloat(
  context.contentResolver,
  Settings.Global.ANIMATOR_DURATION_SCALE
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The animation duration scale is &lt;code&gt;0f&lt;/code&gt; if reduced motion-setting is enabled. &lt;/p&gt;
&lt;p&gt;However, if the reduced motion setting is not enabled, that setting can be undefined. So, to check the value, we&amp;#39;ll need a catch-block, where we set the value to basically anything than &lt;code&gt;0f&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val animationDuration = try {
  Settings.Global.getFloat(
    context.contentResolver,
    Settings.Global.ANIMATOR_DURATION_SCALE
  )
} catch (e: Settings.SettingNotFoundException) {
  1f
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, to check if reduced motion is enabled, we can check if the animation duration is &lt;code&gt;0f&lt;/code&gt;. Here&amp;#39;s the complete function for that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun isReducedMotionEnabled(): Boolean {
  val animationDuration = try {
    Settings.Global.getFloat(context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE)
  } catch (e: Settings.SettingNotFoundException) {
    1f
  }
  return animationDuration == 0f
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So, even though most animations on Android respect the reduced motion setting out of the box, it&amp;#39;s good to be aware of the possibility of situations when it needs to be respected. &lt;/p&gt;
&lt;p&gt;I hope you&amp;#39;ve learned something from this blog post, and in the future, remember that animations are only a delight for some. &lt;/p&gt;
&lt;h2 id=&quot;read-more&quot;&gt;Read More&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://link.springer.com/book/10.1007/978-1-4842-5814-9&quot;&gt;Book by Rob Whitaker: Developing Inclusive Mobile Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/eevajonnapanula/you-make-my-head-spin-reducing-the-motion-on-web-328b&quot;&gt;My blog post: You Make My Head Spin - Reducing the Motion on Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://alistapart.com/article/designing-safer-web-animation-for-motion-sensitivity/&quot;&gt;Article by Val Head: Designing Safer Web Animation For Motion Sensitivity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.theverge.com/23191768/animation-accessibility-neurodivergence&quot;&gt;Article by s. e. smith: My war on animation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Year in Review - 2022 Edition</title>
    <link href="https://eevis.codes/blog/2023-01-07/year-in-review-2022-edition/" />
    <updated>2023-01-07T05:30:27.797Z</updated>
    <id>https://eevis.codes/blog/2023-01-07/year-in-review-2022-edition/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1m0PzAPleYPdxmSLa1LOOU/ab23f7188ce07b8e2f63acce876a5bf5/year_in_review_2022.png"/>]]>
      &lt;p&gt;Another year has come to its end, and it&amp;#39;s time to look back and reflect on it. I wrote a similar post last year: &lt;a href=&quot;https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/&quot;&gt;Year in Review - 2021 edition&lt;/a&gt;, and it was interesting to compare these years.&lt;/p&gt;
&lt;p&gt;This blog post reflects on my 2022 - highs and lows, interesting new things, and some hard times. It doesn&amp;#39;t include everything, but it has many important things that happened last year. Let&amp;#39;s dive in. &lt;/p&gt;
&lt;h2 id=&quot;accomplishments&quot;&gt;Accomplishments&lt;/h2&gt;
&lt;p&gt;Looking back on 2022, I can see some real highlights and accomplishments. Many materialized this year and have been in the works for years. I am proud of them, and I&amp;#39;ve earned them. &lt;/p&gt;
&lt;h3 id=&quot;wtm-ambassador&quot;&gt;WTM Ambassador&lt;/h3&gt;
&lt;p&gt;In November, I got a mail that I was selected as Women Techmakers Ambassador. What is WTM Ambassador, you ask? Here&amp;#39;s how &lt;a href=&quot;https://developers.google.com/womentechmakers/ambassadors&quot;&gt;Women Techmakers&amp;#39;&lt;/a&gt; site defines the role:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Women Techmakers Ambassadors are leaders around the world who are passionate about empowering their communities through organizing events, public speaking, creating content, and mentoring. With access to a global community and exclusive resources, Ambassadors are helping build a world where all women can thrive in tech.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;#39;s an honor and a great recognition of my work in past years. Not everyone gets selected, so it means that I&amp;#39;ve done something right.&lt;/p&gt;
&lt;h3 id=&quot;nwita-nominee&quot;&gt;NWITA Nominee&lt;/h3&gt;
&lt;p&gt;Last year (2021), I was one of the finalists in the Nordic Women in Tech Award&amp;#39;s category, Rising Star of the Year, and this year, I was nominated in the category Developer of the Year. I didn&amp;#39;t make it to the final five, but the nomination is an honor. &lt;/p&gt;
&lt;h3 id=&quot;dev-top-author&quot;&gt;Dev Top Author&lt;/h3&gt;
&lt;p&gt;To my surprise, I got an email that I&amp;#39;m one of this year&amp;#39;s Dev&amp;#39;s Top Authors. Being recognized for the time and effort I&amp;#39;ve put in writing my blog feels good. &lt;/p&gt;
&lt;h3 id=&quot;career-switch&quot;&gt;Career Switch&lt;/h3&gt;
&lt;p&gt;One of the most significant turns this year has taken is my switch from frontend to Android development. To be honest, I was already a bit tired of frontend things. I already knew so much, and even though there&amp;#39;s always something new to learn, I&amp;#39;ve realized I wanted something new. And Android development has been just that.&lt;/p&gt;
&lt;h2 id=&quot;blogging&quot;&gt;Blogging&lt;/h2&gt;
&lt;p&gt;I&amp;#39;ve written some blog posts this year too. I haven&amp;#39;t been as productive as in 2021, but I&amp;#39;m quite happy with what I&amp;#39;ve written.  &lt;/p&gt;
&lt;p&gt;Three of my blog posts got to Dev&amp;#39;s week&amp;#39;s top 7. Interestingly, they were quite different from each other: The first was my International Women&amp;#39;s Day post (&lt;a href=&quot;https://dev.to/eevajonnapanula/nevertheless-eevis-still-codes-53o0&quot;&gt;Nevertheless, Eevis Still Codes&lt;/a&gt;), the second was Results of Quick Testing of Documentation Tools&amp;#39; Accessibility, and the third was Hey Tech Recruiter, Here Are Some Tips from a Developer.&lt;/p&gt;
&lt;p&gt;My personal favorite posts for this year are:&lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Results of Quick Testing of Documentation Tools&#39; Accessibility&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2022-08-11&quot;&gt;August 11th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2022-08-11/results-of-quick-testing-of-documentation-tools-accessibility/&quot;&gt;Read the post Results of Quick Testing of Documentation Tools&#39; Accessibility&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;I tested four different documentation tools (Swagger, Read the Docs, Docusaurus, and GitBook) and used some accessibility testing tools and techniques with them. In the blog post, I summarise my findings from these tests. Writing this blog post was fun because I like to do these accessibility tests.&lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Hey Tech Recruiter, Here Are Some Tips from a Developer&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2022-11-06&quot;&gt;November 6th&lt;/time&gt;&lt;/p&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-11-06/hey-tech-recruiter-here-are-some-tips-from-a-developer/&quot;&gt;Read the post Hey Tech Recruiter, Here Are Some Tips from a Developer&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;One of the most popular posts for this year was this one. It started as a LinkedIn post, and in November, I finally had the time to write it into a whole blog post. I share some frustrations I&amp;#39;ve faced when communicating with recruiters, and this blog post resonated with many developers. &lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Android, Animations and Reduced Motion&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2022-12-12&quot;&gt;December 12th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Read the post Android, Animations and Reduced Motion&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;This blog post is the first I&amp;#39;ve written about Android that&amp;#39;s about something other than learning Android development. As the reduced motion/animations off-theme is close to me personally, I wanted to understand how I can respect that setting on Android. And as always, I love to share what I&amp;#39;ve learned, so I wrote a blog post about it.&lt;/p&gt;
&lt;h2 id=&quot;speaking&quot;&gt;Speaking&lt;/h2&gt;
&lt;p&gt;I did some speaking, and for the first time in a while, some talks I gave in person.&lt;/p&gt;
&lt;p&gt;I was talking in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Turku &amp;lt;3 Frontend&lt;/li&gt;
&lt;li&gt;Women in Tech Summit&lt;/li&gt;
&lt;li&gt;React Native EU&lt;/li&gt;
&lt;li&gt;React Finland&lt;/li&gt;
&lt;li&gt;Tyttöjen päivä&lt;/li&gt;
&lt;li&gt;Twig the Code&lt;/li&gt;
&lt;li&gt;We are developers live&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also had to cancel a couple of events:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Meetup in Helsinki&lt;/li&gt;
&lt;li&gt;Modern Frontends&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first meetup I&amp;#39;ve ever spoken at was in January 2020. And as we all know, Covid hit pretty soon after - and as I&amp;#39;ve continued speaking after that, it&amp;#39;s been all remote until spring 2022. The first in-person meetup was Turku &amp;lt;3 Frontend in April, and let me tell you, I was nervous! I had thought that I was way better at speaking in front of a camera, not seeing the listeners, but I surprised myself with how well speaking in front of a live audience went and how good it felt.&lt;/p&gt;
&lt;p&gt;The number of events for 2022 was lower than for 2021. However, I&amp;#39;m happy about that, and I&amp;#39;m glad about how my speaking year of 2022 turned out. I wouldn&amp;#39;t have the energy for more.&lt;/p&gt;
&lt;h2 id=&quot;projects&quot;&gt;Projects&lt;/h2&gt;
&lt;p&gt;This year, I created some (web) projects. The first was neule.art, a visualizer for knitting patterns. It has one pattern (an Icelandic sweater), but I hope I&amp;#39;ll have some time and inspiration to add other garments. If you want to read about the progress, I wrote a blog post about it: &lt;a href=&quot;https://eevis.codes/blog/2022-06-30/how-i-created-neule-art/&quot;&gt;How I created Neule.art&lt;/a&gt;. Neule.art is made with Eleventy and doesn&amp;#39;t use client-side JavaScript.&lt;/p&gt;
&lt;p&gt;Another project was a guest book app for my sister and her friends for their birthday party. It was created with React and used AWS for storing pictures. I wrote a blog post about that project: &lt;a href=&quot;https://eevis.codes/blog/2022-08-24/learnings-from-creating-a-guest-book-app/&quot;&gt;Learnings From Creating a Guest Book App&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The third project for this year was an accessibility-themed advent calendar. It was created with Eleventy, too, and used Github Actions to reveal each day&amp;#39;s content. I&amp;#39;ve been writing a blog post about it and will publish it probably in January.&lt;/p&gt;
&lt;h2 id=&quot;helping-with-theses&quot;&gt;Helping with Theses&lt;/h2&gt;
&lt;p&gt;I&amp;#39;ve had the honor of helping with three different theses - one master&amp;#39;s thesis and two bachelor&amp;#39;s theses. My role in the progress has been as an accessibility specialist. Depending on the thesis, I&amp;#39;ve answered more general or technical questions.&lt;/p&gt;
&lt;h2 id=&quot;things-you-probably-didnt-see&quot;&gt;Things You Probably Didn&amp;#39;t See&lt;/h2&gt;
&lt;p&gt;I could copy some of the lines from last year&amp;#39;s post here. I&amp;#39;ve been struggling with the same things - I had to take some days off because of burnout symptoms. In the summer, I wrote about the theme a bit: &lt;a href=&quot;https://eevis.codes/blog/2022-07-15/on-the-edge-of-burnout-again/&quot;&gt;On the Edge of Burnout... Again&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Even though I wrote in the blog post that I&amp;#39;m better now, the following months weren&amp;#39;t easy. But after some changes at work (like switching roles to Android development), things have started to look better step by step. The feeling during the weekends has turned into &amp;quot;Monday doesn&amp;#39;t suck.&amp;quot;  I&amp;#39;m even excited about the projects I&amp;#39;m working on and what the future will bring.  &lt;/p&gt;
&lt;p&gt;I also spent lots of time outdoors. I visited five new national parks and acquired a kayaking instructor title. And on the last day of the year, I ordered my own kayak, which I&amp;#39;m super excited about, and I can&amp;#39;t wait for it to arrive. &lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&amp;#39;s Next?&lt;/h2&gt;
&lt;p&gt;I&amp;#39;ve learned not to promise too much - the world changes so fast that I cannot know where I will be in a year. But from what I know now, I will learn and code more Android, I will probably write my master&amp;#39;s thesis with an accessibility theme, and I definitely will spend some time in a kayak!&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/&quot;&gt;Year in Review - 2021 edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/womentechmakers/ambassadors&quot;&gt;Women Techmakers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/eevajonnapanula/nevertheless-eevis-still-codes-53o0&quot;&gt;Nevertheless, Eevis Still Codes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-08-11/results-of-quick-testing-of-documentation-tools-accessibility/&quot;&gt;Read the post Results of Quick Testing of Documentation Tools&amp;#39; Accessibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-11-06/hey-tech-recruiter-here-are-some-tips-from-a-developer/&quot;&gt;Read the post Hey Tech Recruiter, Here Are Some Tips from a Developer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Read the post Android, Animations and Reduced Motion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-06-30/how-i-created-neule-art/&quot;&gt;How I created Neule.art&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-08-24/learnings-from-creating-a-guest-book-app/&quot;&gt;Learnings From Creating a Guest Book App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-07-15/on-the-edge-of-burnout-again/&quot;&gt;On the Edge of Burnout... Again&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Automating Advent Calendar with Github Actions and Eleventy</title>
    <link href="https://eevis.codes/blog/2023-01-14/automating-advent-calendar-with-github-actions-and-eleventy/" />
    <updated>2023-01-14T13:26:11.992Z</updated>
    <id>https://eevis.codes/blog/2023-01-14/automating-advent-calendar-with-github-actions-and-eleventy/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/4K7jxnPBR1Ul4bJ1mS6yjl/de7f719976f589a129b73af7d4dac33d/advent-calendar.png"/>]]>
      &lt;p&gt;I&amp;#39;ve always seen advent/Christmas calendars as an interesting, technical challenge. How to build something that hides content until some specified time? And to do that without client-side JavaScript? And automatically, so I don&amp;#39;t need to deploy the page daily manually? &lt;/p&gt;
&lt;p&gt;Last year, I decided to take up the challenge and build an advent calendar. After some consideration, the concept was clear: it would be in Finnish and about accessibility. I have a Finnish Instagram account where I talk about accessibility, so this combination made the most sense. &lt;/p&gt;
&lt;p&gt;After that, there was all the technical decisions and solving the problem above: How to keep content hidden until the right time? In this blog post, I&amp;#39;ll share how I solved that problem. But first, let&amp;#39;s look into the stack I chose to use.&lt;/p&gt;
&lt;h2 id=&quot;the-stack&quot;&gt;The Stack&lt;/h2&gt;
&lt;p&gt;The calendar is built with Eleventy - my go-to choice when I want to create something that&amp;#39;s not so complicated that I need to break it into smaller components. Actually, now that I think of it, I&amp;#39;d still take Eleventy - my projects are usually simple. &lt;/p&gt;
&lt;p&gt;Other than that, I&amp;#39;m using plain CSS and no client-side JavaScript. I love how you can do that with Eleventy. So, if you&amp;#39;re wondering how I&amp;#39;m doing &amp;quot;not showing the content before the correct date&amp;quot;: No, it&amp;#39;s not with JavaScript. It&amp;#39;s a combination of Eleventy&amp;#39;s features, a private repository, and Github Actions. Let&amp;#39;s talk about them next.&lt;/p&gt;
&lt;h2 id=&quot;hiding-content-with-eleventy&quot;&gt;Hiding Content with Eleventy&lt;/h2&gt;
&lt;p&gt;I spent quite some time trying to come up with a solution that would solve my problem. I could have used client-side JavaScript, but then again, if I could solve that without it, it would be great.&lt;/p&gt;
&lt;p&gt;So I started doing some research. Is there a way to prevent some pages from getting into the build Eleventy does? After searching with some terms that did not give me good results, I finally found the solution: &lt;code&gt;.eleventyignore&lt;/code&gt; and having an &lt;code&gt;unpublished&lt;/code&gt;-directory containing the unpublished content. This way, the contents of the &lt;code&gt;unpublished&lt;/code&gt;-directory won&amp;#39;t be part of the build.&lt;/p&gt;
&lt;p&gt;Yes, I felt a bit stupid after discovering this ignore file because that was not the first thing I thought. I mean, every JS project and package has one, so why didn&amp;#39;t I think about it straight away? Well, that happens. I am sharing this because, usually, these blog posts are about results, not progress.&lt;/p&gt;
&lt;p&gt;Back to the story, I now had a way to hide the following days. The next thing was to write a script to move the current day&amp;#39;s content from one folder (&lt;code&gt;unpublished&lt;/code&gt;) to another (&lt;code&gt;2022&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;As I&amp;#39;ve been using Node for many years, I used it for that. I could have gone with the shell commands, but my shell scripting skills are way rustier than Node skills, so I went with the easiest solution. &lt;/p&gt;
&lt;p&gt;After a while, I had a small function to handle moving the file. The function takes the current date as a parameter. It checks if the &lt;code&gt;unpublished&lt;/code&gt; folder contains a file with that date and moves it to the &lt;code&gt;2022&lt;/code&gt;-folder.&lt;/p&gt;
&lt;p&gt;The next thing was to set up Github Action to run the script daily, commit it to the main branch and rebuild the site.&lt;/p&gt;
&lt;h2 id=&quot;github-actions&quot;&gt;Github Actions&lt;/h2&gt;
&lt;p&gt;For the calendar app to work correctly, there were three noteworthy aspects of writing the Github Action file:
Running the workflow daily
Running the Node script mentioned in the previous section
Committing changes to the main branch. &lt;/p&gt;
&lt;p&gt;For running the workflow daily, I already had an example of a schedule-event with cron in another project, so that was a little copy-paste. &lt;a href=&quot;https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows&quot;&gt;Github has good documentation around triggering events on workflows&lt;/a&gt;, and I wanted to run the workflow every night at 00:00 UTC, so these are the lines I used:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;  schedule:
    - cron:  &amp;#39;0 0 * * *&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second task, running the Node script, was also straightforward. Defining the script in &lt;code&gt;package.json&lt;/code&gt;´s scripts made it effortless to run in the workflow.&lt;/p&gt;
&lt;p&gt;I used the shell scripts for the third task because I had an example of that in another project. I first defined the committers email and name with&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;git config --global user.email &amp;quot;updater-bot&amp;quot;
git config --global user.name &amp;quot;updater-bot&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And after that, I added the moved files and committed them with&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;git add 2022 unpublished
git commit -m &amp;quot;Move day&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and finally pushed the branch to the main with&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;git push origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the commit hit the main branch, the project got rebuilt, and the new changes were published.&lt;/p&gt;
&lt;h2 id=&quot;the-content&quot;&gt;The Content&lt;/h2&gt;
&lt;p&gt;Writing the content was the most fun and time-consuming part of the project. I decided to go with three types of content: Information, task, and book recommendation. &lt;/p&gt;
&lt;p&gt;I ended up having 14 days of information, seven days of tasks, and three book recommendations. My goal was to gather material from different sources, with different types, and mainly in Finnish. And as I wanted to provide something new for a wider audience, I avoided technical content. &lt;/p&gt;
&lt;h2 id=&quot;sharing-the-calendar&quot;&gt;Sharing the Calendar&lt;/h2&gt;
&lt;p&gt;At the beginning of December, I shared the calendar on my social media accounts: Instagram and LinkedIn. The initial response was tremendous and showed in analytics; there was a clear spike in the visits to the page. Looking at the analytics, there were shares on (assumed) work settings, as some traffic came from Slack and Teams. &lt;/p&gt;
&lt;p&gt;After the initial sharing, I shared each day&amp;#39;s content daily on my Instagram account. I could have advertised the calendar way more, but my December was so busy at work, and I didn&amp;#39;t have the energy. It shows in the analytics; most of the traffic came from Instagram.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;This project was a fun one to complete. It was also the first web project I&amp;#39;ve created since switching to Android development, so I was interested in seeing if I&amp;#39;d totally forgotten how to web. I hadn&amp;#39;t - but I still like Kotlin more than JavaScript &lt;/p&gt;
&lt;p&gt;Do you have technical challenges you&amp;#39;ve been thinking about for a longer time and want to solve? Or something seen as an intriguing challenge (but not necessarily wanting to solve yourself)?&lt;/p&gt;
&lt;p&gt;And oh, if you want to see the calendar, it&amp;#39;s &lt;a href=&quot;https://saavutettavuusjoulukalenteri.eevis.codes/&quot;&gt;Saavutettavuusjoulukalenteri&lt;/a&gt;.&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Sometimes I Feel Like I&#39;m Invisible - Experiences of a Woman in Tech</title>
    <link href="https://eevis.codes/blog/2023-02-05/sometimes-i-feel-like-im-invisible-experiences-of-a-woman-in-tech/" />
    <updated>2023-02-05T17:26:01.720Z</updated>
    <id>https://eevis.codes/blog/2023-02-05/sometimes-i-feel-like-im-invisible-experiences-of-a-woman-in-tech/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/7IVPGIOMJrKd5X9Mf98sIB/8a69dbc6372eec43dde00ab24acdf919/invisible.png"/>]]>
      &lt;p&gt;&lt;em&gt;This blog post is very personal and sharing it makes me very vulnerable. So please, respect that. And please, just please don&amp;#39;t come and tell me it&amp;#39;s all my fault or that I have imagined it all. Because I haven&amp;#39;t.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Throughout my career, I&amp;#39;ve felt invisible. It&amp;#39;s these small things - My man colleague being asked about a feature, even though I&amp;#39;m leading its development, or someone forgetting to add my team of one into their slide deck where every other team in the company is included. Or when the men in the room constantly speak over me. Or when it&amp;#39;s known that I&amp;#39;m the most knowledgeable person on a certain topic, and still a man specifically asking another man in the group to answer his advanced questions on that topic.&lt;/p&gt;
&lt;p&gt;As an isolated incident, it&amp;#39;s not that bad - I can understand that communication hasn&amp;#39;t worked as it should have, and someone didn&amp;#39;t know I was responsible or that my team even existed. Or that a man just happened to ask another man this time. &lt;/p&gt;
&lt;p&gt;But when it happens regularly from multiple people, pretty much in every workplace I&amp;#39;ve ever worked in tech, it hurts. It hurts like hell. I feel like I&amp;#39;m invisible, like I or my work doesn&amp;#39;t matter. I feel like I&amp;#39;m fighting against something intangible I cannot win because it&amp;#39;s too powerful. &lt;/p&gt;
&lt;p&gt;Sometimes, after a long week full of these occasions, which have left me feeling invisible, I can&amp;#39;t do anything else but cry because I&amp;#39;m so freaking exhausted. I feel so powerless.&lt;/p&gt;
&lt;p&gt;And what happens if I try to speak up? Well, I&amp;#39;m that angry woman - or just dismissed or forgotten. Or I get greeted with, &amp;quot;Well that person clearly didn&amp;#39;t mean it that way.&amp;quot; Maybe what hurts the most is when someone says (repeatedly) that they&amp;#39;re going to do something about it, but they never do anything.&lt;/p&gt;
&lt;p&gt;Of course, this doesn&amp;#39;t happen from everyone or in every instance, but it has happened regularly enough for me to notice it. I remember my first job in tech and how some people treated me like I didn&amp;#39;t know anything and that I never would. At the time, I assumed that that&amp;#39;s how it is, but reflecting on those encounters has made me understand that it wasn&amp;#39;t me; it was them. &lt;/p&gt;
&lt;p&gt;I remember being dismissed, not listened to, and sometimes completely ignored in the jobs following the first. I&amp;#39;ve been left out of a meeting invite as the only woman in the group. I also remember being called too aggressive and too angry. &lt;/p&gt;
&lt;p&gt;And I&amp;#39;m not the only one. Women and other minorities in tech face these same experiences all over the world. And there is a word for it: Microaggressions. &lt;/p&gt;
&lt;h2 id=&quot;microaggressions&quot;&gt;Microaggressions&lt;/h2&gt;
&lt;p&gt;Many of the things I&amp;#39;ve described above are microaggressions. Kevin Nadal, who is a professor in psychology at John Jay College of Criminal Justice, defines microaggressions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Microaggressions are defined as the everyday, subtle, intentional — and oftentimes unintentional — interactions or behaviors that communicate some sort of bias toward historically marginalized groups.&lt;/p&gt;
&lt;p&gt;The difference between microaggressions and overt discrimination or macroaggressions, is that people who commit microaggressions might not even be aware of them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Source: &lt;a href=&quot;https://www.npr.org/2020/06/08/872371063/microaggressions-are-a-big-deal-how-to-talk-them-out-and-when-to-walk-away&quot;&gt;Andrew Limbong: Microaggressions are a big deal: How to talk them out and when to walk away&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Jennifer Y. Kim and Alyson Meister have researched microaggressions women face in STEM workplaces. In the article, they first define what microaggressions are and how they can be classified. Let&amp;#39;s have a closer look.&lt;/p&gt;
&lt;p&gt;Microaggressions can be classified into three types, ranging in levels of subtlety: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;microassault&lt;/li&gt;
&lt;li&gt;microinsult&lt;/li&gt;
&lt;li&gt;microinvalidation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first one, microassault, explicitly demeans women, like, by calling them a &amp;quot;bitch&amp;quot;. Microinsults, on the other hand, are more subtle comments or behaviors - such as when a manager calls only man employees to the meeting. In this case, the subtle message is that man&amp;#39;s work is more valuable. &lt;/p&gt;
&lt;p&gt;The third one, microinvalidation, is comments or behaviors negating women&amp;#39;s experiences with gender discrimination. Examples include things like gaslighting (&amp;quot;Sexism is a thing of a past!&amp;quot;) and invalidating the lived experience of women. (Source: &lt;a href=&quot;https://link.springer.com/article/10.1007/s10551-022-05203-0&quot;&gt;Jennifer Y. Kim &amp;amp; Alyson Meister: Microaggressions, Interrupted: The Experience and Effects of Gender Microaggressions for Women in STEM&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Further, they write about problems these microaggressions cause for women in STEM. Problems include threats to woman&amp;#39;s identity, as they&amp;#39;re subtly challenging their legitimacy as leaders, a burden by the difficulty of decoding microaggressions, and the effects on one&amp;#39;s (mental) health - burnout is real. &lt;/p&gt;
&lt;p&gt;For the research, they interviewed 39 women leaders in STEM in North America with various backgrounds. From the material, they found  five types of microaggressions: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Devaluation of technical competence&lt;/li&gt;
&lt;li&gt;Devaluation of physical presence&lt;/li&gt;
&lt;li&gt;Denial of one&amp;#39;s reality&lt;/li&gt;
&lt;li&gt;Pathologizing one&amp;#39;s character&lt;/li&gt;
&lt;li&gt;Pathologizing one&amp;#39;s gender&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other theme they were researching was the meaning of allyship. Let&amp;#39;s talk about that in the next section.&lt;/p&gt;
&lt;h2 id=&quot;the-meaning-of-allyship&quot;&gt;The Meaning of Allyship&lt;/h2&gt;
&lt;p&gt;Kim and Meister found in their research that the intervention from allies was essential in keeping women from leaving STEM fields. I couldn&amp;#39;t agree more. Reading the research paper, I could also put some feelings into words and understand why an ally stepping up has felt so great.&lt;/p&gt;
&lt;p&gt;Usually, allies are defined as someone from the dominant group. In the case of gender and tech, by that definition, allies are often men. It&amp;#39;s imperative that members of the dominant group help with the efforts to bring more equality, as the non-dominant group alone can&amp;#39;t change the status quo. &lt;/p&gt;
&lt;p&gt;However, in their study, Kim and Meister defined allies and ally interventions more broadly, including, e.g., more senior women stepping up in the events of microaggressions. And that&amp;#39;s how I mean allyship in this blog post - something anyone can and, depending on the situation (e.g., assuming it&amp;#39;s safe), should do.&lt;/p&gt;
&lt;p&gt;The types of interventions from allies Kim and Meister found fell into two categories: domain-related and person-related. The first, domain-related, was about acknowledging one&amp;#39;s technical skills (e.g., vouching publicly for a woman&amp;#39;s technical skills after someone had doubted or minimized them). The second one, person-related, was about acknowledging the microaggressions women face and validating the experiences of women.&lt;/p&gt;
&lt;p&gt;From my experience, the interventions of allies have often helped me understand and handle the situation and reframe it in my mind. I&amp;#39;ve recognized that I wasn&amp;#39;t imagining the problem: someone else has noticed it too. And further, as Kim and Meister write, it has interrupted the internalization process - otherwise, I would have internalized the thoughts about me not being competent enough, of me not belonging in tech. &lt;/p&gt;
&lt;p&gt;This is the call to action to anyone who can: When you see these microaggressions happening, step up and talk about it - especially if you are a leader or in a more senior position.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Microaggressions suck. As isolated incidents, they&amp;#39;re small things, but when they happen repeatedly, they build this colossal load which can eventually lead to health problems and women and other minorities leaving tech. So we need to do better.&lt;/p&gt;
&lt;p&gt;Think about your work environment. What could you do to prevent microaggressions from happening?&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npr.org/2020/06/08/872371063/microaggressions-are-a-big-deal-how-to-talk-them-out-and-when-to-walk-away&quot;&gt;Andrew Limbong: Microaggressions are a big deal: How to talk them out and when to walk away&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://link.springer.com/article/10.1007/s10551-022-05203-0&quot;&gt;Jennifer Y. Kim &amp;amp; Alyson Meister: Microaggressions, Interrupted: The Experience and Effects of Gender Microaggressions for Women in STEM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Improving Android Accessibility with Modifiers in Jetpack Compose </title>
    <link href="https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/" />
    <updated>2023-07-11T05:56:24.800Z</updated>
    <id>https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/5LoaFBDSmX0FosTf4M1wpd/5000ad0bbd52306af6129777e83d3541/modifiers_accessibility__1_.png"/>]]>
      &lt;p&gt;The other day, I was doing some accessibility fixes on our codebase. I came across a switch that didn&amp;#39;t have a label associated with it, meaning screen reader users would need to do a bit of guessing to get the information on what they were toggling. I wanted to fix that and started researching. &lt;/p&gt;
&lt;p&gt;At one point, I was going through the list of modifiers for composable components and noticed that many could help make the app more accessible. I got so excited! And I wanted to share some of these modifiers with you, so here we are. I&amp;#39;m going to go through these modifiers: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toggleable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;selectable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clickable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;magnifier&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But first, let&amp;#39;s talk a bit about modifiers in general and what they are.&lt;/p&gt;
&lt;h2 id=&quot;what-are-modifiers&quot;&gt;What are Modifiers?&lt;/h2&gt;
&lt;p&gt;As the Android Developers site defines modifiers:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modifiers allow you to decorate or augment a composable. Modifiers let you do these sorts of things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change the composable&amp;#39;s size, layout, behavior, and appearance&lt;/li&gt;
&lt;li&gt;Add information, like accessibility labels&lt;/li&gt;
&lt;li&gt;Process user input&lt;/li&gt;
&lt;li&gt;Add high-level interactions, like making an element clickable, 
scrollable, draggable, or zoomable&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Source: &lt;a href=&quot;https://developer.android.com/jetpack/compose/modifiers&quot;&gt;Android Developers: Compose modifiers&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;So they are a way to modify composables&amp;#39; looks, behavior, and semantics. They&amp;#39;re used a lot in modern Android development with Jetpack Compose, so if you&amp;#39;ve ever tried Compose, you&amp;#39;ve probably come across them.&lt;/p&gt;
&lt;p&gt;As the modifiers can do many things, we can use them for improving accessibility as well. Let&amp;#39;s talk about that more in the next section.&lt;/p&gt;
&lt;h2 id=&quot;modifiers-for-accessibility&quot;&gt;Modifiers for Accessibility&lt;/h2&gt;
&lt;p&gt;There are many use cases for different modifiers to improve the accessibility of our apps. For example, with modifiers, we can reduce the amount of tab stops for users that use switch devices, hardware keyboards, or screen readers, or we can add more semantic information for assistive technology users in general. &lt;/p&gt;
&lt;p&gt;We can add visual cues such as focus indicators or improve the visibility and feedback of touch interactions. A good example is what we will do later in this post by increasing the touch target size; we&amp;#39;re also increasing the size of the ripple effect to give better feedback on actions to anyone using the app. &lt;/p&gt;
&lt;p&gt;One thing I always want to emphasize when discussing accessibility is that when building your app, remember to test it with different assistive technologies. You might find that some things are not working as expected - that&amp;#39;s precisely what happened to me when I was writing the code for this blog post.&lt;/p&gt;
&lt;p&gt;Oh, and speaking about the code, if you want to see it, you can find &lt;a href=&quot;https://github.com/eevajonnapanula/modifiers-example-app&quot;&gt;the repository for Modifiers Example App here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this section, we will look closer at the modifiers I listed at the beginning of the blog post. Let&amp;#39;s start with &lt;code&gt;clickable.&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;clickable&quot;&gt;Clickable&lt;/h3&gt;
&lt;p&gt;Clickable-modifier, as the name states, makes the element clickable. This utility can be useful when creating, e.g., lists of items that need to be clickable or if some larger area needs to be clickable. &lt;/p&gt;
&lt;p&gt;However, if you&amp;#39;re creating a plain ol&amp;#39; button, I advise using the &lt;code&gt;Button&lt;/code&gt;-component because it already has the semantics and behaviors built in and is accessible by default. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s consider an example of a list where the user needs to be able to bookmark an item on the list. This picture shows what we are building:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/26Mu60wqpzb0ghGHeF7ynS/4f320375013d34fdb4b539d042c09839/Screenshot_2023-07-10_at_8.57.48.png&quot; alt=&quot;A pink rectangle with rounded corners, which has the text &amp;quot;Bookmark this item&amp;quot; on the left side, and at the end (right side), there is an outlined icon representing a bookmark.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As seen in the picture, there is a row with text (&amp;quot;Bookmark this item&amp;quot;) and a bookmark icon. One option is to wrap the icon with the &lt;code&gt;IconButton&lt;/code&gt; component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Row(
    modifier = Modifier
        .fillMaxWidth()
        .clip(MaterialTheme.shapes.large)
        .background(MaterialTheme.colorScheme.primaryContainer)
        .padding(ClickableScreen.padding),
    horizontalArrangement = Arrangement.SpaceBetween,
    verticalAlignment = Alignment.CenterVertically,
) {
    Text(
        text = &amp;quot;Bookmark this item&amp;quot;, 
        style = MaterialTheme.typography.titleMedium, 
        color = MaterialTheme.colorScheme.onPrimaryContainer
      )
      IconButton(onClick = { onClick() }) {
          FavouriteIcon(
              icon = icon, 
              contentDescription = &amp;quot;Bookmark this item&amp;quot;
          )
      }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code snippet would work, and when a user taps the icon button, the item would get bookmarked. However, the area where the user can tap is small. It would be easier to tap the whole row.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s make a couple of changes: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Row(
    modifier = Modifier
        .fillMaxWidth()
        .clip(MaterialTheme.shapes.large)
        .background(MaterialTheme.colorScheme.primaryContainer)
        // Add clickable-modifier with the role of Button
        .clickable(
                role = Role.Button,
          ) {
                onClick()
          }
        .padding(ClickableScreen.padding),
    horizontalArrangement = Arrangement.SpaceBetween,
    verticalAlignment = Alignment.CenterVertically,
) {
    Text(
        text = &amp;quot;Bookmark this item&amp;quot;, 
        style = MaterialTheme.typography.titleMedium, 
        color = MaterialTheme.colorScheme.onPrimaryContainer
    )
  // Remove IconButton. 
  // The content description is not needed anymore 
  // as the whole element is merged and text serves as a label 
   FavouriteIcon(icon = icon, modifier = Modifier.padding(12.dp))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This change improves the accessibility of the modifier a lot. It increases the touch target size and adds the role of a button so that assistive technology users will know it&amp;#39;s a button. &lt;/p&gt;
&lt;p&gt;This modifier works well if you need to perform a single click. However, if you&amp;#39;ll need a double click and/or a long click in addition to a single click, &lt;code&gt;combinedClickable&lt;/code&gt; is your friend. &lt;/p&gt;
&lt;h3 id=&quot;selectable&quot;&gt;Selectable&lt;/h3&gt;
&lt;p&gt;Another modifier we can use for improving accessibility and usability is &lt;code&gt;selectable.&lt;/code&gt; It&amp;#39;s often used as part of a mutually exclusive group - such as a radio button group. &lt;/p&gt;
&lt;p&gt;There are a couple of benefits to wrapping the whole row with this modifier (instead of, e.g., using a button or &lt;code&gt;clickable&lt;/code&gt;-modifier): The area where a user can tap and select the item is larger, you can add correct semantics for the element so that assistive technology users get relevant information and the items can be grouped easily. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s talk about how to improve an example I&amp;#39;ve seen in several places. The picture shows what we&amp;#39;re building:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/QOF0xGo0R4jgbzEc3Iz1Y/7b7cccb2c8a38424738c903389645046/Screenshot_2023-07-10_at_8.58.00.png&quot; alt=&quot;Two rows with pink background, text on the left, and a radio button on the right. The first one is selected and has the text &amp;quot;Option A,&amp;quot; and the second one is not and has the text &amp;quot;Option B&amp;quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And here&amp;#39;s the simplified code (not copied from any of the codebases mentioned above):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun RadioInputRow(text: String, checked: Boolean, onChange: (String) -&amp;gt; Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(
            text = text, 
            style = MaterialTheme.typography.labelLarge, 
            color = MaterialTheme.colorScheme.onPrimaryContainer
        )
        RadioButton(
            selected = checked,
            onClick = { onChange(text) },
            colors = RadioButtonDefaults.colors(
                selectedColor = MaterialTheme.colorScheme.tertiary,
                unselectedColor = MaterialTheme.colorScheme.tertiary,
            ),
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code snippet works, but a user needs to be able to tap the radio button, which has a small touch target size. Let&amp;#39;s make a couple of modifications to make the code more accessible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun RadioInputRow(
    text: String, 
    checked: Boolean, 
    onChange: (String) -&amp;gt; Unit
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
        // We add a selectable modifier with state and role to the row
           .selectable(
               selected = checked,
               enabled = true,
               role = Role.RadioButton,
               onClick = { onChange(text) },
            )
            .padding(16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(
            text = text, 
            style = MaterialTheme.typography.labelLarge, 
            color = MaterialTheme.colorScheme.onPrimaryContainer
        )
        RadioButton(
        // We clear the semantics from
        // the RadioButton, so that the user, 
        // who is using assistive technology,
        // doesn&amp;#39;t have an extra tab stop on the way.
            modifier = Modifier.clearAndSetSemantics {},
            selected = checked,
            onClick = { onChange(text) },
            colors = RadioButtonDefaults.colors(
                selectedColor = MaterialTheme.colorScheme.tertiary,
                unselectedColor = MaterialTheme.colorScheme.tertiary,
            ),
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, the click area is more extensive, and it&amp;#39;s easier to hit and select the target. For this example, I decided to keep the Material Theme&amp;#39;s &lt;code&gt;RadioButton&lt;/code&gt;-component, but we could also switch it to two icons (selected and unselected), the same way as in the &lt;code&gt;clickable&lt;/code&gt;-example. &lt;/p&gt;
&lt;p&gt;There are a couple of things to note about the semantics of this example. First, the whole row gets the &lt;code&gt;selectable&lt;/code&gt;-modifier, and we pass the state (enabled, selected) to it, and we also pass the role of a switch to it. This way, we can ensure that assistive technology gets correct information about the element, and users will know how to interact with it.&lt;/p&gt;
&lt;p&gt;Another aspect related to semantics is to clear all semantics from the original &lt;code&gt;RadioButton&lt;/code&gt;-element. If we don&amp;#39;t do it, the user will encounter the same component with the same info twice - first on the container (&lt;code&gt;Row&lt;/code&gt;) and then with the radio button. That also means an extra tab stop for anyone navigating with assistive technology using focus order.&lt;/p&gt;
&lt;p&gt;We want to make one more change and add the &lt;code&gt;selectableGroup&lt;/code&gt;-modifier to the parent component that is wrapping all the radio input rows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Column(
    modifier = Modifier
        ...
    // Adding the selectableGroup here:
        .selectableGroup(),
    verticalArrangement = Arrangement.SpaceBetween,
) {
    inputs.map { input -&amp;gt;
        RadioInputRow(
            text = input, 
            checked = selected == input, 
            onChange = { selected = it }
        )
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, users using, e.g., a screen reader, get the information that there is x number of items in the list as an option to select. &lt;/p&gt;
&lt;h3 id=&quot;toggleable&quot;&gt;Toggleable&lt;/h3&gt;
&lt;p&gt;Like &lt;code&gt;selectable,&lt;/code&gt; the &lt;code&gt;toggleable&lt;/code&gt;- modifier helps make components more accessible. &lt;code&gt;toggleable&lt;/code&gt; can be used when there is a state with two values - like selecting a setting with an option to be on or off. &lt;/p&gt;
&lt;p&gt;For this example, we have a row with text and a toggle at the end:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/6WzysUn3rGCcGirnIoFR4m/60ea5cde87aafdea3a6c6e901eef92db/Screenshot_2023-07-10_at_8.58.12.png&quot; alt=&quot;A pink rectangle with round corners, and it has the text &amp;quot;Toggleable&amp;quot; at the start (left) side of the row and a switch toggle at the end (right side) of the row.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And the code we have at first looks like this: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun SwitchRow(
    text: String, 
    checked: Boolean, 
    onChange: (Boolean) -&amp;gt; Unit
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(
            text = text, 
            style = MaterialTheme.typography.labelLarge, 
            color = MaterialTheme.colorScheme.onPrimaryContainer
        )
        Switch(
            checked = checked, 
        onCheckedChange = { onChange(it) }
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this code snippet, the touch target is only the area of the Material Theme&amp;#39;s &lt;code&gt;Switch&lt;/code&gt;-component, as it was with the example of the &lt;code&gt;selectable&lt;/code&gt;-modifier. Also, other similar problems (as mentioned in the &lt;code&gt;selectable&lt;/code&gt;-section) are present. So, we&amp;#39;ll make a couple of changes to make this component more accessible: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun SwitchRow(
    text: String, 
    checked: Boolean, 
    onChange: (Boolean) -&amp;gt; Unit
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
        // We add the toggleable modifier
        // with the state (value) and role
            .toggleable(
                 value = checked,
                 role = Role.Switch,
                 onValueChange = { onChange(it) },
            )
            .padding(16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(
            text = text, 
            style = MaterialTheme.typography.labelLarge, 
            color = MaterialTheme.colorScheme.onPrimaryContainer
        )
        Switch(
              checked = checked, 
              onCheckedChange = { onChange(it) }, 
              // We clear the semantics
              modifier = Modifier.clearAndSetSemantics {}
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It has a value attribute as part of its state. For &lt;code&gt;toggleable,&lt;/code&gt; the role is usually &lt;code&gt;Switch&lt;/code&gt; - compared to &lt;code&gt;selectable&lt;/code&gt;&amp;#39;s  &lt;code&gt;RadioButton.&lt;/code&gt; &lt;/p&gt;
&lt;h3 id=&quot;magnification&quot;&gt;Magnification&lt;/h3&gt;
&lt;p&gt;The final modifier in this blog post is different from the others. You can use this modifier for magnification in your app. However, it&amp;#39;s important to note that in many cases, the operating system&amp;#39;s magnification is sufficient, and people who need it know how to use it (and do use it). But this might be useful in some cases, not just for those who use the os&amp;#39;s magnification but for anyone who needs to magnify a part of your UI.&lt;/p&gt;
&lt;p&gt;For the &lt;code&gt;magnifier&lt;/code&gt; modifier to work correctly, &lt;a href=&quot;https://developer.android.com/reference/android/widget/Magnifier.html&quot;&gt;the Android&amp;#39;s Magnifier widget&lt;/a&gt; needs to have support on the device. Before proceeding, we&amp;#39;ll want to check that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;if (!MagnifierStyle.Default.isSupported) {
    Text(&amp;quot;Magnifier is not supported on this platform.&amp;quot;)
} else {
    // TODO
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;magnifier&lt;/code&gt;-modifier needs to know the center of the magnified area. We can get that information from the &lt;code&gt;pointerInput&lt;/code&gt;-modifier&amp;#39;s &lt;code&gt;dragDetectedGestures&lt;/code&gt; function. We want to store the value of the center to a variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;var magnifierCenter by remember { 
    mutableStateOf(Offset.Unspecified) 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we&amp;#39;ll wrap the area we want a user to be able to magnify with a &lt;code&gt;Box&lt;/code&gt; element and set the modifiers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Box(
    Modifier
        // Set the source center and styles for the magnifier
        .magnifier(
            sourceCenter = { magnifierCenter },
            zoom = 3f,
            style = MagnifierStyle(
                size = DpSize(height = 200.dp, width = 300.dp),
            ),
        )
        .pointerInput(Unit) {
            detectDragGestures(
                // Show the magnifier in the initial position
                onDragStart = { magnifierCenter = it },
                // Magnifier follows the pointer during a drag event
                onDrag = { _, delta -&amp;gt;
                    magnifierCenter = magnifierCenter.plus(delta)
                },
                // Hide the magnifier when a user ends the drag movement.
                onDragEnd = { magnifierCenter = Offset.Unspecified },
                onDragCancel = { magnifierCenter = Offset.Unspecified },
            )
        },
) {
    Text(
        &amp;quot;Try magnifying this text by dragging a pointer (finger, mouse, other) over the text.&amp;quot;,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, with this code snippet, this is what we get: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/3Ju0id6tNJ2FwheVdjzdjl/46dc39e95d505ac0fb538635de4b8212/magnifier.png&quot; alt=&quot;A screen where the magnifier covers part of the text, and the visible text is &amp;quot;t by dragging&amp;quot; and &amp;quot;over the text.&amp;quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve discussed modifiers and accessibility in general, and we&amp;#39;ve looked more closely into four modifiers: &lt;code&gt;clickable,&lt;/code&gt; &lt;code&gt;selectable,&lt;/code&gt; &lt;code&gt;toggleable,&lt;/code&gt; and &lt;code&gt;magnifier.&lt;/code&gt; This post was not a comprehensive one on accessibility and modifiers, and I&amp;#39;ve intentionally left out some topics like focus management and hardware keyboards entirely. I&amp;#39;m planning to write blog posts about those themes later.&lt;/p&gt;
&lt;p&gt;Do you have any thoughts, ideas, or questions? Let me know!&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Android Developers and Accessibility - Challenges and Proposed Solutions</title>
    <link href="https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/" />
    <updated>2023-07-17T04:21:15.578Z</updated>
    <id>https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/5sqLmlMCyTYrtJIjT2Mbz2/cd421aa65e171087c22eaa89b68f23eb/challenges-and-solutions__1_.png"/>]]>
      &lt;p&gt;My current Big Project is my master&amp;#39;s thesis - I&amp;#39;m writing about Android accessibility, and my goal is to create a list of checks Android developers could use to create more accessible apps. I&amp;#39;m super excited about the theme, and once it&amp;#39;s ready, I will definitely share the results on my blog. &lt;/p&gt;
&lt;p&gt;One of the main theoretical themes in the thesis is how Android developers implement accessibility and what challenges prevent them from doing so. Research also provides some solutions to these challenges. &lt;/p&gt;
&lt;p&gt;In this blog post, we&amp;#39;ll first look through some of the challenges Android developers face (although these are common for other types of development as well) and then discuss proposed solutions.&lt;/p&gt;
&lt;h2 id=&quot;the-challenges&quot;&gt;The Challenges&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s first look at the challenges developers face. I&amp;#39;ve listed all the resources used in the &lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#resources&quot;&gt;Resources section&lt;/a&gt;. The list has three studies about Android developers, their accessibility knowledge, and their willingness and readiness to develop accessible apps.&lt;/p&gt;
&lt;h3 id=&quot;not-knowing-about-accessibility-and-lack-of-awareness&quot;&gt;Not Knowing About Accessibility and Lack of Awareness&lt;/h3&gt;
&lt;p&gt;The first problem I want to mention is that Android developers often lack accessibility knowledge. This lack of familiarity varies - for some, it&amp;#39;s total unawareness, but for most, it shows as problems understanding the exact needs of disabled users. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This finding is something I can confirm from my own experiences. Often Android developers are familiar with the fact that accessibility should be part of an app. Still, the exact implementation and why they must do something are unclear. &lt;/p&gt;
&lt;h3 id=&quot;companies-ignoring-accessibility-requirements&quot;&gt;Companies Ignoring Accessibility Requirements&lt;/h3&gt;
&lt;p&gt;Another aspect of the lack of awareness is that companies ignore the accessibility requirements. Patel et al. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; found out that the leadership in companies often prioritized other things over accessibility when a deadline was approaching. They usually see accessibility as an extra cost rather than an opportunity. This attitude is also visible in the internal policies, so for developers, it&amp;#39;s often hard to find time to fix accessibility issues retroactively. &lt;/p&gt;
&lt;p&gt;Another thing that is related to fixing accessibility as an afterthought is that it can be considered hard - and it might lead to a situation where those accessibility issues don&amp;#39;t get fixed. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I can again confirm these findings - throughout my career, I&amp;#39;ve witnessed situations where accessibility is not part of the company&amp;#39;s requirements, which means there is no time or resources to consider accessibility.&lt;/p&gt;
&lt;h3 id=&quot;little-to-no-exposure-or-background-with-accessibility&quot;&gt;Little to No Exposure or Background with Accessibility&lt;/h3&gt;
&lt;p&gt;Vendome et al. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; found out that developers often don&amp;#39;t have exposure or background with accessibility and assistive technology, and that leads to misunderstandings, e.g., in questions developers ask on platforms like Stack Overflow. For example, they might ask about how TalkBack reads phone numbers and how to force it to read them in a particular way. (For those unfamiliar with TalkBack: TalkBack users can control this with settings, and developers should not force it on apps.)&lt;/p&gt;
&lt;p&gt;On the other hand, opportunities to interact with people with disabilities were considered as a helpful way to understand user expectations better. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; I think this all comes down to the fact that it&amp;#39;s helpful to talk to users to understand their expectations and needs. It&amp;#39;s a crucial part of creating good apps.&lt;/p&gt;
&lt;h3 id=&quot;accessibility-is-only-about-screen-reader-accessibility&quot;&gt;Accessibility is Only About Screen Reader Accessibility&lt;/h3&gt;
&lt;p&gt;One challenge for accessibility is that it&amp;#39;s mostly about screen reader accessibility. Vendome et al. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; found out that the most discussed topic on Stack Overflow (in the material they collected) was screen reader accessibility. &lt;/p&gt;
&lt;p&gt;I can second that finding from my experience - when discussing accessibility, it&amp;#39;s often about screen reader accessibility. And it&amp;#39;s an important aspect, but it&amp;#39;s necessary to remember that it&amp;#39;s not all. Considering interaction for people who can see or have low vision is as important as is, e.g., cognitive accessibility.&lt;/p&gt;
&lt;h3 id=&quot;belief-of-accessibility-having-negative-effects-on-the-apps-aesthetics-and-usability&quot;&gt;Belief of Accessibility Having Negative Effects on the App&amp;#39;s Aesthetics and Usability&lt;/h3&gt;
&lt;p&gt;Another finding I&amp;#39;ve witnessed is that some developers (and designers) believe that if they incorporate accessibility into their apps or designs, it will affect the app&amp;#39;s aesthetics and/or usability. Di Gregorio et al. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; found that developers might meet ideas about progressive functionality or interfaces negatively because they can add complexity to the app.&lt;/p&gt;
&lt;p&gt;As a developer, I can understand where this comes from. Especially requirements added as an afterthought do increase complexity - so it&amp;#39;s important to include accessibility right from the start, and building these interfaces is less work.&lt;/p&gt;
&lt;h3 id=&quot;lack-of-tools&quot;&gt;Lack of Tools&lt;/h3&gt;
&lt;p&gt;The second to last finding is the lack of tools for developing accessible apps. Also, some developers have had situations where free tools have disappeared suddenly, and there has been nothing to replace them. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;/p&gt;
&lt;p&gt;Another thing related to different tools (or assistive technology) is that developers often don&amp;#39;t have experience with those tools, which can lead to misunderstandings and solving problems that are not problems. One example is the problem described in the section &amp;quot;Little to No Exposure or Background with Accessibility&amp;quot; about TalkBack and numbers. TalkBack handles how numbers are presented, and users can alter that, but because developers didn&amp;#39;t know it, they tried to force their app to represent numbers in a certain way when using TalkBack. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;From the perspective of a mobile dev with a background in web development, I agree that there aren&amp;#39;t enough tools for developing accessible Android apps. There are way more tools for accessibility in web development, but for Android, not so much. The situation is changing, but slowly. I&amp;#39;ve also seen the lack of exposure to assistive technology and how it can lead to interesting outcomes.&lt;/p&gt;
&lt;h3 id=&quot;relevant-and-usable-information-is-hard-to-find&quot;&gt;Relevant and Usable Information is Hard to Find&lt;/h3&gt;
&lt;p&gt;The last finding is that it&amp;#39;s hard to find relevant and usable information. One interviewee from Di Gregorio et al.&amp;#39;s research noted that it&amp;#39;s not just about unawareness of accessibility guidelines but also about not being able to find usable information from the internet to fix the issues. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; Patel et al. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; also discovered a lack of resources - especially for building accessible components. And one of their study participants noted that the available accessibility guidelines should be in a form that is more digestible so that developers would use them.&lt;/p&gt;
&lt;p&gt;I can fully agree. Especially with Android development, I often know what I should do regarding how the app should behave to be more accessible. Still, it&amp;#39;s hard to find usable articles on &lt;i&gt;how&lt;/i&gt; actually do the technical implementation. So I can only imagine how hard and frustrating it can feel for someone with less background in accessibility. &lt;/p&gt;
&lt;p&gt;We&amp;#39;ve gone through many challenges Android developers face, and let&amp;#39;s look next at the solutions proposed in the research. These solutions don&amp;#39;t solve every aspect, but at least some of them.  &lt;/p&gt;
&lt;h2 id=&quot;proposed-solutions&quot;&gt;Proposed Solutions&lt;/h2&gt;
&lt;p&gt;The three research articles I&amp;#39;ve looked at list two types of proposed solutions to solve the challenges of building accessible apps for Android: Better tools and education. Let&amp;#39;s look at both of them more.&lt;/p&gt;
&lt;h3 id=&quot;better-tools-for-development&quot;&gt;Better Tools for Development&lt;/h3&gt;
&lt;p&gt;Both Patel et al. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; and Vendome et al. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; suggest having better tools for development to address accessibility in specific technical implementations. These tools could be integrated with, e.g., IDEs and could (or from what I&amp;#39;ve observed and talked with other developers, should) be semi-automated. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve been following the Android development ecosystem for a while now, and there are ongoing processes to improve the tooling. There are some code checks in Android Studio for XML-based views, but they&amp;#39;ve been lacking from Compose components. However, in Google I/O, they announced that there would be improvements for Compose previews in the form of these accessibility warnings. (Source: &lt;a href=&quot;https://goo.gle/IO23_AndroidAccess&quot;&gt;What&amp;#39;s new in Android Accessibility&lt;/a&gt;)&lt;/p&gt;
&lt;h3 id=&quot;increase-accessibility-knowledge-through-formal-education&quot;&gt;Increase Accessibility Knowledge Through (Formal) Education&lt;/h3&gt;
&lt;p&gt;Another proposed solution is to increase accessibility knowledge through education. That could be achieved by adding more accessibility-related topics to curriculums of non-computing fields. Also, within computer science and related fields, learning about addressing accessibility issues during development would be beneficial. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This solution goes past formal education - developers would generally benefit from learning more about universal design principles and targeted tutorials and workshops addressing specific accessibility topics. &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;sup&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/#3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;From my perspective, on both formal and informal education, I&amp;#39;ve been happy to notice the increase of accessibility-themed articles and workshops, as well as accessibility in the curriculums of, e.g., universities. There is, of course, a lot to be done, but the direction is correct.&lt;/p&gt;
&lt;p&gt;But even if we had many quality articles and other resources, it&amp;#39;s always each developer&amp;#39;s responsibility to learn more about the topic. Any amount of education or materials is only enough if we, as developers, take the opportunity to learn more. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, I&amp;#39;ve gone through some challenges Android developers face regarding accessibility per prior research. I&amp;#39;ve discussed lack of awareness, companies ignoring accessibility requirements, developers having little to no exposure or background with accessibility, accessibility being only about screen reader accessibility, the belief that accessibility makes the app less usable or beautiful, lack of tools, and that relevant and usable information is hard to find. &lt;/p&gt;
&lt;p&gt; I&amp;#39;ve also shared some suggested solutions: better tooling for development and increasing accessibility knowledge through formal and informal education. &lt;/p&gt;
&lt;p&gt;Do you recognize these challenges in your work? Or do you have additional solution ideas? &lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li id=&quot;1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt; Di Gregorio, M., Di Nucci, D., Palomba, F., &amp; Vitiello, G. (2022). The making of accessible Android applications: An empirical study on the state of the practice. Empirical Software Engineering, 27(6), 145. &lt;a href=&quot;https://doi.org/10.1007/s10664-022-10182-x&quot;&gt;https://doi.org/10.1007/s10664-022-10182-x&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;2&quot;&gt;&lt;sup&gt;2&lt;/sup&gt; Patel, R., Breton, P., Baker, C. M., El-Glaly, Y. N., &amp; Shinohara, K. (2020). Why Software is Not Accessible: Technology Professionals&#39; Perspectives and Challenges. Extended Abstracts of the 2020 CHI Conference on Human Factors in Computing Systems, 1–9. &lt;a href=&quot;https://doi.org/10.1145/3334480.3383103&quot;&gt;https://doi.org/10.1145/3334480.3383103&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;3&quot;&gt;&lt;sup&gt;3&lt;/sup&gt; Vendome, C., Solano, D., Liñán, S., &amp; Linares-Vásquez, M. (2019). Can Everyone use my app? An Empirical Study on Accessibility in Android Apps. 2019 IEEE International Conference on Software Maintenance and Evolution (ICSME), 41–52. &lt;a href=&quot;https://doi.org/10.1109/ICSME.2019.00014&quot;&gt;https://doi.org/10.1109/ICSME.2019.00014&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content
    >
  </entry>
   
  
  <entry>
    <title>More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description</title>
    <link href="https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/" />
    <updated>2023-07-24T09:03:19.422Z</updated>
    <id>https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/6tWE5hbm2m2olZnHb8VfNA/dec6d96861a8f1c12ac887911edd4ee3/more-accessible-graphs-1.png"/>]]>
      &lt;p&gt;Data visualizations rely on the visual representation of data. And that data is usually portrayed by some combination of colors. On mobile apps, they also often include different interactions that rely on touch. &lt;/p&gt;
&lt;p&gt;But what if your user can&amp;#39;t see? Or if they&amp;#39;re colorblind, and the combination of colors is not visible to them? Or if they use a switch device or hardware keyboard for navigation?&lt;/p&gt;
&lt;p&gt;From my experience, data visualizations of apps are often inaccessible for these groups of users, for instance. I wanted to experiment with how much changes and code would be needed to make a line chart more accessible. To my surprise, the amount of code wasn&amp;#39;t that much. Of course, experimenting took some time, but after solving these problems, I now have an example project I can utilize in other projects.&lt;/p&gt;
&lt;p&gt;And because I love to share what I&amp;#39;ve learned, I&amp;#39;m writing this series of blog posts to help you to build more accessible graphs with Jetpack Compose. We&amp;#39;ll look at three different aspects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding information for non-visual access users (so, e.g., a TalkBack user)&lt;/li&gt;
&lt;li&gt;Adding keyboard interaction in addition to touch-based interaction&lt;/li&gt;
&lt;li&gt;Differentiating data by other means than just color&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This first blog post in the series is about adding information for non-visual access users, e.g., screen reader users. The following two blog posts will cover adding keyboard interaction and differentiating data with other means than just color. There might be additional posts about voice access and increasing touch target size in the future.&lt;/p&gt;
&lt;p&gt;My goal is not to provide a final solution but ideas you can take and improve to use in your codebase. Of course, graphs can be more complex, and these relatively simple solutions might not work for everything, but I hope they&amp;#39;ll give you pointers on improving your graph&amp;#39;s accessibility.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s first talk about the example project I prepared for this experiment.&lt;/p&gt;
&lt;h2 id=&quot;the-initial-project-code&quot;&gt;The Initial Project Code&lt;/h2&gt;
&lt;p&gt;So, before starting any explorations on making things more accessible, I needed to build a small example app. You can find all the code in this blog post from the &lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/&quot;&gt;Graph Example-project repository&lt;/a&gt;. The &lt;code&gt;main&lt;/code&gt;-branch contains the final version with all the changes, and the &lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/tree/starting-point&quot;&gt;&lt;code&gt;starting-point&lt;/code&gt;-branch&lt;/a&gt; has the initial code I started tweaking.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s a short video on what I built:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/ZHIMzyIPuomkiPmCrlxYs/573cef254ce48f72e302d972a103a8a7/final-starting-point.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;The app has a line graph containing data from woman applicants in Finnish higher education per year (starting from 2015) from two fields: Information Communication Technology and Engineering (non-ICT). The graph shows the percentage of women applicants for these fields individually and displays the total percentages for these two fields.&lt;/p&gt;
&lt;p&gt;When a user touches and horizontally drags a pointer over the graph, the selected year&amp;#39;s percentages are shown in the bottom right corner. These values are not available in any other way - so if a user can&amp;#39;t use a pointer, they would miss this information. &lt;/p&gt;
&lt;p&gt;Technically, this graph is built with the Canvas-API, which adds some restrictions on how to add, e.g., content description to elements. And that&amp;#39;s something we want to do - as that is one way to communicate the values to someone who can&amp;#39;t see the texts. Let&amp;#39;s next look at how we can add them to the graph.&lt;/p&gt;
&lt;h2 id=&quot;adding-content-descriptions-to-items-on-graph&quot;&gt;Adding Content Descriptions to Items on Graph&lt;/h2&gt;
&lt;p&gt;Because of how the graph is utilizing Canvas-API, it means that it&amp;#39;s completely hidden from accessibility services. In practice, it means that someone using, e.g., TalkBack can&amp;#39;t access the values inside the graph.
Also, because the labels on x- and y-axes are built with the &lt;code&gt;drawText&lt;/code&gt;-method, they&amp;#39;re unavailable. Here&amp;#39;s an example of how TalkBack reads through the screen:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/2HreTsGzqBzKthDIcFsO7E/d239c0a550fb294e5622bb67e28f4535/Screen_Recording_20230723_071526_GraphExample.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;On the video, I&amp;#39;m navigating through the screen with TalkBack. The video shows that the cursor entirely skips the graph. It reads the title before and the labels under the graph but nothing from it. Let&amp;#39;s start improving the experience by adding a &lt;code&gt;Highlighter&lt;/code&gt;-component.&lt;/p&gt;
&lt;h3 id=&quot;add-highlighter-component&quot;&gt;Add &lt;code&gt;Highlighter&lt;/code&gt;-Component&lt;/h3&gt;
&lt;p&gt;In this case, the &lt;code&gt;Highlighter&lt;/code&gt;-component is an overlay over the graph, highlighting the selected section, and should be visible only when focused. Technically, it&amp;#39;s a &lt;code&gt;Box&lt;/code&gt; that overlays the whole graph, with smaller &lt;code&gt;Box&lt;/code&gt;-elements that are the size of the highlighted area. This component also helps improve the keyboard and switch interaction, as we&amp;#39;ll see in the later blog post.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Highlighter&lt;/code&gt;-component looks like this: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun Highlighter(
    modifier: Modifier = Modifier,
    widthBetweenPoints: Float,
    pixelPointsForTotal: List&amp;lt;Point&amp;gt;,
    pixelPointsForTech: List&amp;lt;Point&amp;gt;,
    pixelPointsForIct: List&amp;lt;Point&amp;gt;,
    highlightedX: Float?
) {
    Box(
        modifier
            .fillMaxSize(),
    ) {
        val sectionWidth = with(LocalDensity.current) {
            widthBetweenPoints.toDp()
        }

        pixelPointsForTotal.forEachIndexed { index, point -&amp;gt;
            val xOffset = ((index + 1) * widthBetweenPoints - widthBetweenPoints * 0.66f).toInt()
            var isHighlighted by remember { mutableStateOf(false) }
            var position by remember { mutableStateOf(Pair(0f, 0f)) }

            if (highlightedX == null) isHighlighted = false

            highlightedX?.let {
                isHighlighted = it &amp;gt; (position.first - widthBetweenPoints) &amp;amp;&amp;amp; it &amp;lt; (position.second - widthBetweenPoints)
            }

            Box(
                modifier = Modifier
                    .fillMaxHeight()
                    .width(sectionWidth)
                    .offset { IntOffset(xOffset, 0) }
                    .border(
                        width = Graph.Highlighter.width,
                        color = if (isHighlighted) MaterialTheme.colorScheme.onBackground else Color.Transparent,
                        shape = RoundedCornerShape(Graph.Highlighter.borderRadius),
                    )
                    .onGloballyPositioned {
                        position =
                            Pair(
                it.positionInParent().x, 
                it.positionInParent().x + it.size.width
              )
                    }
            ) {
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It takes the following parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;modifier: Modifier - a modifier to pass down styles. Defaults to &lt;code&gt;Modifier&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;widthBetweenPoints: Float - as the name states, width between the points. It&amp;#39;s used to position the highlighting section correctly &lt;/li&gt;
&lt;li&gt;pixelPointsForTotal: List&lt;Point&gt; - List of values for total as &lt;code&gt;Point&lt;/code&gt;&lt;/Point&gt;&lt;/li&gt;
&lt;li&gt;pixelPointsForTech: List&lt;Point&gt; - List of values for engineering as &lt;code&gt;Point&lt;/code&gt; (should be renamed to &lt;code&gt;pixelPointsForEng&lt;/code&gt;)&lt;/Point&gt;&lt;/li&gt;
&lt;li&gt;pixelPointsForIct: List&lt;Point&gt; - List of values for ICT-field as &lt;code&gt;Point&lt;/code&gt;&lt;/Point&gt;&lt;/li&gt;
&lt;li&gt;highlightedX: Float? - x-value of the currently highlighted item&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;info&quot;&gt;
&lt;p&gt;Note: The &lt;code&gt;Point&lt;/code&gt; is a data class defined in the code, and the definition looks like this:&lt;/p&gt;
&lt;pre&gt;
&lt;code class=&quot;language-kotlin&quot;&gt;
data class Point(
    val x: Float,
    val y: Float,
    val year: Int,
    val percentage: Float,
    val isHighlighted: Boolean = false,
) {
    val percentageString = &quot;${percentage.toInt()} %&quot;
}
&lt;/code&gt;
&lt;/pre&gt;
&lt;/aside&gt;

&lt;p&gt;Inside the component, all the pixel point values for the total (but this could be any of the lists) are mapped, and the lambda returns a highlighting section for each point. The offset for each section is calculated with the help of &lt;code&gt;widthBetweenPoints.&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;We&amp;#39;ll also need to get the position of the highlighter. The values are the start and end x-coordinates of the component. We&amp;#39;ll save it to state, and on the &lt;code&gt;Box&lt;/code&gt;-component, we change the value on the &lt;code&gt;onGloballyPositioned&lt;/code&gt;-modifier:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;var position by remember { mutableStateOf(Pair(0f, 0f)) }
...

.onGloballyPositioned {
    position =
        Pair(
            it.positionInParent().x, 
            it.positionInParent().x + it.size.width
        )
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then use these values to determine if the currently selected year (the &lt;code&gt;highlightedX&lt;/code&gt;-parameter) is inside the area of this highlighter component.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;var isHighlighted by remember { mutableStateOf(false) }
...
if (highlightedX == null) isHighlighted = false

highlightedX?.let {
    isHighlighted = 
        it &amp;gt; (position.first - widthBetweenPoints) 
        &amp;amp;&amp;amp; it &amp;lt; (position.second - widthBetweenPoints)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code snippet, &lt;code&gt;isHighlighted&lt;/code&gt; stores the value of whether the pointer is inside its area. If &lt;code&gt;highlightedX&lt;/code&gt; is null (there&amp;#39;s no pointer input on the graph), then &lt;code&gt;isHighlighted&lt;/code&gt; is false. &lt;/p&gt;
&lt;p&gt;We can then use&lt;code&gt;isHighlighted&lt;/code&gt; to change the border color of the area in the &lt;code&gt;border&lt;/code&gt;-modifier:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;.border(
    width = Graph.Highlighter.width,
    color = if (isHighlighted) MaterialTheme.colorScheme.onBackground else Color.Transparent,
    shape = RoundedCornerShape(Graph.Highlighter.borderRadius),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This image shows the current state when the year 2019 is highlighted:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/3eI40qnknkwMZePlBmQrTJ/a46b6e55a27fe4b3a9229873eb3117d9/Screenshot_20230723_072659.png&quot; alt=&quot;Year 2019 from the graph has a white rectangle around it, and the points for each graph are also rectangles instead of circles. At the right bottom of the graph values are visible: 2019: All: 27%, Eng.: 26% and ICT: 28%.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;We now have a highlighter component. The current implementation just outlines the current selection when using pointer input, so it&amp;#39;s not improving accessibility that much just yet. Let&amp;#39;s next add some content descriptions to make the graph more accessible.&lt;/p&gt;
&lt;h3 id=&quot;add-content-description&quot;&gt;Add Content Description&lt;/h3&gt;
&lt;p&gt;Next, we want to add a content description for anyone using assistive technology such as TalkBack. We first want to add the &lt;code&gt;focusable&lt;/code&gt;- modifier to each highlighter&amp;#39;s child element. As the name states, it makes the element focusable, meaning people with different assistive technologies can reach it. Without it, the element is hidden from a screen reader and not focusable with, e.g., a hardware keyboard or switch device.  &lt;/p&gt;
&lt;p&gt;The second step is to add the content description for each highlighter&amp;#39;s child element. Because we use the &lt;code&gt;Box&lt;/code&gt;-component, we need to use the &lt;code&gt;semantics&lt;/code&gt;-modifier&amp;#39;s &lt;code&gt;contentDescription&lt;/code&gt;-property - contrary to the &lt;code&gt;contentDescription&lt;/code&gt;-property available for some components (such as &lt;code&gt;Image&lt;/code&gt;s). &lt;/p&gt;
&lt;p&gt;As each highlighted section displays the year and values for ICT, engineering, and total, that&amp;#39;s what we want to add to the content description as it is the relevant information for that section. We first form the content description inside &lt;code&gt;pixelPointsForTotal.forEachIndexed {}&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val contentDesc = 
    &amp;quot;${point.year}: &amp;quot; +
    &amp;quot;${stringResource(id = R.string.all)} ${point.percentageString}, &amp;quot; +
    &amp;quot;${stringResource(id = R.string.eng)} ${pixelPointsForTech[index].percentageString}, &amp;quot; +
    &amp;quot;${stringResource(id = R.string.ict)} ${pixelPointsForIct[index].percentageString}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we add the &lt;code&gt;focusable&lt;/code&gt; modifier and use the content description we defined in the &lt;code&gt;semantics&lt;/code&gt;-modifier:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;...
    .focusable()
    .semantics {
        contentDescription = contentDesc
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, when a user focuses on a highlighted section with a screen reader, they would hear (or get in Braille) the following:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/5pAdMf1raWtYA8gY5kkHxT/38abd7cf519363fcd4a4cec087968927/Screen_Recording_20230723_072037_GraphExample.mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/42aba1bdd4cc15ccd21881a61b9ce46185c86a73&quot;&gt;complete difference in code for this section is available in this PR&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked at the initial code of the Graph Example project and how it&amp;#39;s built. We then added a highlighter component to help us with our task of creating more accessible graphs. Finally, we added a content description for each highlighted section.&lt;/p&gt;
&lt;p&gt;Please remember that this code is simplified, and when applying it to your codebase, you&amp;#39;ll probably need to find out how to tweak it to work correctly.  &lt;/p&gt;
&lt;p&gt;Do you have any questions, comments, or feedback, or want to say anything else? Please share; I&amp;#39;d like to hear it!&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/&quot;&gt;Graph Example-project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/tree/starting-point&quot;&gt;&lt;code&gt;starting-point&lt;/code&gt;-branch&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/42aba1bdd4cc15ccd21881a61b9ce46185c86a73&quot;&gt;Complete difference in code for this section is available in this PR&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>More Accessible Graphs with Jetpack Compose Part 2: Adding Keyboard Interaction</title>
    <link href="https://eevis.codes/blog/2023-07-31/more-accessible-graphs-with-jetpack-compose-part-2-adding-keyboard-interaction/" />
    <updated>2023-07-31T06:00:12.392Z</updated>
    <id>https://eevis.codes/blog/2023-07-31/more-accessible-graphs-with-jetpack-compose-part-2-adding-keyboard-interaction/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/6NRAksRlj6aU1J2vsoydVR/1e29303844480df742b3482389a38950/more-accessible-graphs-2.png"/>]]>
      &lt;p&gt;Welcome to the second episode of &amp;quot;More Accessible Graphs with Jetpack Compose&amp;quot; - in this blog post, we&amp;#39;ll continue from where we left off in the first one. We will look at improving the (physical) keyboard access for graphs in Jetpack Compose. Link to the first part: &lt;a href=&quot;https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;At the end of the blog post, I mentioned that the second part would be about keyboard and switch access. After testing my code for the blog post, I realized that the switch access part didn&amp;#39;t work. After some deep-diving into documentation, I admitted I wouldn&amp;#39;t add it to this post as it would require a lot of research. So there likely will be at least four parts in this &amp;quot;More Accessible Graphs with Jetpack Compose&amp;quot;-blog post series.&lt;/p&gt;
&lt;p&gt;In the first blog post, we added a &lt;code&gt;Higlighter&lt;/code&gt;-component, an overlay on top of the line graph I had built. Then we made it focusable and added a content description for accessibility services. &lt;/p&gt;
&lt;p&gt;After these changes, a keyboard user can already focus on the different parts of the graph, but there&amp;#39;s one problem: The graph legend is not visible. And you may now wonder if it is really a problem - we added the content description, right?&lt;/p&gt;
&lt;p&gt;The content description is unavailable if you only use a keyboard. If a person, who can see and uses a hardware device for navigation, navigates with the graph in the current state, they probably get frustrated because this is what they would get:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/7jdl1GxtBF2FhxvGpQXJp1/395ee860ee3f62c52b14e742f2296d29/keyboard-start-en.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;They can see that the focus changes but can&amp;#39;t see the individual values. If I encountered something like this, I would think there is a bug in the app. &lt;/p&gt;
&lt;h2 id=&quot;adding-keyboard-interaction&quot;&gt;Adding Keyboard Interaction&lt;/h2&gt;
&lt;p&gt;Users can already focus on each section, so we no longer need to worry about that. But we&amp;#39;ll need a way to change the currently focused position on the x-axis - like if a user was moving their finger on the graph. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;highlightedX&lt;/code&gt;-parameter is the one that controls the currently highlighted position on the x-axis. We&amp;#39;ll need to add a way to change it programmatically when a highlighter section gets focus. Let&amp;#39;s first add a function to do that: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun Highlighter(
    ...
    setFocus: (Float) -&amp;gt; Unit
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the parent component, which stores &lt;code&gt;highlightedX,&lt;/code&gt; we define a function to change the value of highlighted X-position:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Highlighter(
    ...
    setFocus = { newX -&amp;gt;
        highlightedX = newX
    }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, now we have a function to control the x-position and switch the currently focused position on the x-axis. We want to pass the x-position of the currently focused highlighter section to the function, and for that, we attach an &lt;code&gt;onFocusChanged&lt;/code&gt;-modifier.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;.onFocusChanged {
    if (it.isFocused) {
        setFocus(point.x)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Within the modifier, we check if the current section is focused, and if it is, we use the &lt;code&gt;setFocus&lt;/code&gt;-function, passing the x-position of that currently focused section. &lt;/p&gt;
&lt;p&gt;After this change, we already have working code - focusing on a highlighter section that shows the graph label so that a user can see the values for each point on that focused year.&lt;/p&gt;
&lt;p&gt;There is just a minor tweak we still want to do. Right now, the border of the highlighter is visible if there is a focus on the component from both hardware devices and pointer input. We don&amp;#39;t want to show the highlighter component&amp;#39;s border for pointer input because there is already a different highlighter (a dashed line) when a user uses pointer input.&lt;/p&gt;
&lt;p&gt;We will fix that by adding a state for border color, setting it to transparent by default, and then updating it when the focus changes. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;var color by remember { mutableStateOf(Color.Transparent) }
...
// Then on .onFocusChanged-modifier:

.onFocusChanged {
    color = if (it.isFocused) focusedColor else Color.Transparent
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we use that &lt;code&gt;color&lt;/code&gt;-variable for the border color for each highlighter section. &lt;/p&gt;
&lt;p&gt;Now it all works as expected, as seen in this video:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/276754Rs7AbUSxMkUCYoyS/cd83be97e1d48708bff21209c756cc59/keyboard-improved-en.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;The video shows that focus and the label text change when a user navigates by using a keyboard.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/5e98304d1d82b53eea9241c3c8bd7a7b1943a1d4&quot;&gt;You can find all the changes made in this commit.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve developed the &lt;code&gt;Highlighter&lt;/code&gt;-component further to allow interaction with assistive devices like hardware keyboards. This way, a user won&amp;#39;t be relying only on being able to use pointer input, such as touch. &lt;/p&gt;
&lt;p&gt;The third blog post will be about differentiating data with other means than color. &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/5e98304d1d82b53eea9241c3c8bd7a7b1943a1d4&quot;&gt;You can find all the changes made in this commit.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>The Power of Default</title>
    <link href="https://eevis.codes/blog/2023-08-07/the-power-of-default/" />
    <updated>2023-08-07T04:30:31.014Z</updated>
    <id>https://eevis.codes/blog/2023-08-07/the-power-of-default/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/4RsD2zmnyIm79kMP73KNIj/591c909cc8e6c2186d822013a80ceb17/the_power_of_default.png"/>]]>
      &lt;p&gt;&lt;em&gt;Oh, hey there! Before you read this blog post, I want to say that it contains a lot of talk about equality and how some groups of people are not seen as equal compared to others. I&amp;#39;m passionate about changing that. If you know that a feminist point of view makes you angry, I recommend reading but take some deep breaths before commenting. Try to be friendly, like there was actual human reading your comment. Thank you!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There&amp;#39;s a lot of power with the default. And with default, I mean default selections in (digital) services, apps, and others - but also in the fact that some groups of people are seen as the default, &amp;quot;normal&amp;quot;, compared to other groups.&lt;/p&gt;
&lt;p&gt;Why I&amp;#39;m writing about this theme? Well, I believe that we, as developers (or, basically, any role in the tech industry), need to be really conscious of this topic so that we don&amp;#39;t accidentally (or, in some cases, on purpose) exclude, or even discriminate, some of our users. We need to recognize what our decisions can do to actual people. &lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;ll first talk about who is default and who is not, then about the default in tech, and finally, I&amp;#39;ll discuss using the default for good.  &lt;/p&gt;
&lt;h2 id=&quot;who-is-default-and-who-is-not&quot;&gt;Who is Default and Who is Not?&lt;/h2&gt;
&lt;h3 id=&quot;the-default-in-what-we-dont-say&quot;&gt;The Default in What We Don&amp;#39;t Say&lt;/h3&gt;
&lt;p&gt;The default is visible in what we don&amp;#39;t say and what needs to be explicitly said. Let&amp;#39;s take an example from sports: When writing this post, the FIFA Women&amp;#39;s World Cup is ongoing. Do you know what the men&amp;#39;s tournament is called? FIFA World Cup. There is no mention of men; it&amp;#39;s just an assumption that everyone knows it&amp;#39;s about men; it&amp;#39;s the default.&lt;/p&gt;
&lt;p&gt;However, I want to mention that I love how Finnish YLE (the national broadcasting company) is talking just about &amp;quot;Jalkapallon MM 2023&amp;quot;, meaning Football/Soccer World Championships 2023 — so they don&amp;#39;t mention gender.&lt;/p&gt;
&lt;p&gt;That is, of course, a problem for some; some Finnish people have complained online that it should be called &lt;em&gt;women&amp;#39;s&lt;/em&gt; football. Well, the same people have a history of saying that they&amp;#39;re defending women&amp;#39;s rights in sports when they&amp;#39;re just being transphobic. Talk about caring about women&amp;#39;s sports now.&lt;/p&gt;
&lt;p&gt;The other example of the default is the color of skin. Have you ever noticed that when someone is describing a person, they often mention the color of the skin only if it&amp;#39;s not white? It&amp;#39;s as if white skin color is a default.&lt;/p&gt;
&lt;p&gt;The third example I want to highlight is heteronormativity and its defaults. If someone says they&amp;#39;re in love and has not explicitly told that they&amp;#39;re not heterosexual, others often assume that if that person is a woman, the person she&amp;#39;s in love with is a man, and vice versa. To be seen, they&amp;#39;ll need to tell that they&amp;#39;re, e.g., gay/bi/pansexual (or something else). The default assumption is that everyone is heterosexual.&lt;/p&gt;
&lt;p&gt;And yes, heteronormative assumptions often forget that gender is not binary - which makes other genders than men and women invisible. &lt;/p&gt;
&lt;h3 id=&quot;the-default-with-professions&quot;&gt;The Default with Professions&lt;/h3&gt;
&lt;p&gt;As a woman in tech - and especially in a technical role, I constantly face the fact that the word &amp;quot;software developer&amp;quot; is a gendered word for many. There&amp;#39;s this implicit assumption that developers are men. This belief is visible in how people speak - it&amp;#39;s not once or twice when the hypothetical software developer is gendered as &amp;quot;he&amp;quot; (not, e.g., &amp;quot;they). It&amp;#39;s also visible when I meet new people - there have been situations where I&amp;#39;ve assumed to be in a non-technical role, such as HR or marketing, or a designer, just because of my gender. &lt;/p&gt;
&lt;p&gt;And this default with professions is detectable in other disciplines as well. It can be created through words, or it can be implicit. In Finland (and in many other countries), nurses are assumed to be women. Firefighters (no, I&amp;#39;m not going to enforce the stereotype by calling them &amp;quot;firemen&amp;quot;) are often assumed to be men. &lt;/p&gt;
&lt;p&gt;If we talk about high-paying roles, the assumption is that these people are men. There&amp;#39;s this joke I&amp;#39;ve heard: &amp;quot;Women just don&amp;#39;t want to be in the high-paying positions, they choose the lower paying ones, such as woman-CEO, woman-doctor, woman-lawyer, etc.&amp;quot;&lt;/p&gt;
&lt;p&gt;Defaults with professions are usually implied, something that&amp;#39;s not said out loud. I gave examples from gender - but the other aspects have their own defaults. They all work the same way - often whiteness, cis-gender, heterosexuality, living without a disability, and other aspects are assumed.&lt;/p&gt;
&lt;h2 id=&quot;the-default-in-tech&quot;&gt;The Default in Tech&lt;/h2&gt;
&lt;p&gt;The other part of the power of the default I wanted to discuss is the default in and with tech. There are lots of occasions where the default selection affects how we operate - and that&amp;#39;s often intentional. The creators of services know how the default selections affect us, so they utilize them, often for profit. &lt;/p&gt;
&lt;p&gt;Sara Wachter-Boettcher discusses the default settings in her book &amp;quot;Technically Wrong: Sexist Apps, Biased Algorithms, and Other Threats of Toxic Tech.&amp;quot; She writes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Defaults also affect how we perceive our choices, making us more likely to choose whatever is presented as default, and less likely to switch to something else. This is known as the default effect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And this is exploited often. It can be tip amounts defaulting to one of the higher options or preselecting accepting marketing emails. Or it can be the preselection of the pricing option in the middle when subscribing to a service. Of course, defaults can make our paths on these services faster - sometimes, it&amp;#39;s just about that. But unfortunately, often, it&amp;#39;s part of a design to profit more.&lt;/p&gt;
&lt;p&gt;Sara Wachter-Boettcher also writes about the defaults in voice assistants like Apple&amp;#39;s Siri, Google Now, and Amazon&amp;#39;s Alexa. The default voice was a woman&amp;#39;s for a long time for all of them. Apple switched to a non-woman default voice some major updates ago, but the other two have a woman&amp;#39;s voice on by default. &lt;/p&gt;
&lt;p&gt;As these voice assistants are, as the name states, assistants, it&amp;#39;s as if the default gender for assistant, helper, should be a woman. And with this, I mean in the eyes of the creators. &lt;/p&gt;
&lt;p&gt;In general, there&amp;#39;s a lot to unpack about these voice assistants. If you&amp;#39;re interested, there&amp;#39;s a publication about the problematic design of these assistants: &lt;a href=&quot;https://unesdoc.unesco.org/ark:/48223/pf0000367416.page=1&quot;&gt;UNESCO and EQUAL Skills Coalition: I&amp;#39;d blush if I could: closing gender divides in digital skills through education&lt;/a&gt;, specifically the Think Piece 2-chapter. &lt;/p&gt;
&lt;p&gt;And as a final example for this section, have you ever paid attention to the default options of forms when, e.g., registering on a service? If they have default options and ask for gender, the preselection is usually &amp;quot;man.&amp;quot; And if they ask for other things, it&amp;#39;s often one of the abovementioned aspects, which I&amp;#39;ve mentioned as the default. &lt;/p&gt;
&lt;h2 id=&quot;using-the-power-of-default-for-good&quot;&gt;Using the Power of Default for Good&lt;/h2&gt;
&lt;p&gt;The nice thing about the power of default is that we can also utilize it for good. As developers, designers, and in other roles in the tech sector, we can change the defaults to more inclusive options. We can ensure these defaults are not used for discrimination and exclusion.&lt;/p&gt;
&lt;p&gt;We can also affect how the technology itself behaves. As Caroline Criado Perez writes in her famous book &amp;quot;Invisible women&amp;quot;, we have the data about inequality in our systems. &amp;quot;(but) whether or not coders will use it to fix their male-biased algorithms remains to be seen&amp;quot;, she continues. In this case, she was writing about translations and how gender-neutral sentences were translated into English stereotypes - like Finnish &amp;quot;Hän on koodari,&amp;quot; which is gender-neutral, would often be translated into &amp;quot;He is a coder.&amp;quot; &lt;/p&gt;
&lt;p&gt;We have the data about inequality; we have studies; we have what we need. Are we going to work on changing the default, which has for too long been a white, cis-gendered, heterosexual man without disabilities? We have the power to change that. All we need to do is pay attention, educate ourselves, and act.&lt;/p&gt;
&lt;p&gt;Oh, and by the way, if you fall into the categories that I described above being the default, and especially if you fall into all of them: You probably can do the most, so I&amp;#39;m counting on you to work towards a more equal world and using your power to do so!&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://unesdoc.unesco.org/ark:/48223/pf0000367416.page=1&quot;&gt;UNESCO and EQUAL Skills Coalition: I&amp;#39;d blush if I could: closing gender divides in digital skills through education&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>More Accessible Graphs with Jetpack Compose Part 3: Differentiating without Color</title>
    <link href="https://eevis.codes/blog/2023-08-16/more-accessible-graphs-with-jetpack-compose-part-3-differentiating-without/" />
    <updated>2023-08-16T03:43:44.518Z</updated>
    <id>https://eevis.codes/blog/2023-08-16/more-accessible-graphs-with-jetpack-compose-part-3-differentiating-without/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/7aIt8MbcdOKbiUouliYDTa/dd3f2e19dc508ab445943d70fd058370/more-accessible-graphs-3.png"/>]]>
      &lt;p&gt;This blog post is the third one in my series on more accessible graphs with Jetpack Compose. You can find the previous two from the following links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-31/more-accessible-graphs-with-jetpack-compose-part-2-adding-keyboard-interaction/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 2: Adding Keyboard Interaction&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The third topic we will cover is differentiating data by other means than color. Color is a convenient way to distinguish data, but what if you can&amp;#39;t see color? Or what if you see colors differently? For example, the example project we&amp;#39;ve been developing during the blog post series would look like this for someone who can&amp;#39;t see color at all:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/MdDucPsdO0WO3ChjvUqIV/58c95e4f28d3ddb1059b6df2d4838ed5/Screenshot_2023-08-13_at_7.00.51.png&quot; alt=&quot;An example how the app looks for someone who can&#39;t see color at all. Lines are not distinguishable from each other.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;And someone with red-green color blindness:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/3BiLAMvGDnBiBG6DLFPSmB/3c78c1052ea2c79eb61593802adb5220/Screenshot_2023-08-13_at_7.00.32.png&quot; alt=&quot;An example how the app looks for someone with red-green color blindness. Lines are not distinguishable from each other.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;Another aspect of why not use color alone is that phone screens display color differently. A noticeable difference on a high-end external screen for a designer might be almost the same shades for a lower-end phone, making usage impossible. &lt;/p&gt;
&lt;p&gt;There are better ideas than just using color to differentiate data. In the next section, let&amp;#39;s explore some of these other ways.&lt;/p&gt;
&lt;h2 id=&quot;options-for-color&quot;&gt;Options for Color&lt;/h2&gt;
&lt;p&gt;When talking about graphs, there are generally two ways to improve the distinguishability of data: Shapes and patterns. For example, if a bar chart is initially differentiated by color, adding patterns for different colors could differentiate those bars. &lt;/p&gt;
&lt;p&gt;For a line chart, both these options are valid. Let&amp;#39;s explore both and differentiate the data by adding different shapes for points and some patterns for the line. This solution is not the most beautiful because I&amp;#39;m mixing these two options, but it&amp;#39;s there to demonstrate, not to be a final design.&lt;/p&gt;
&lt;h2 id=&quot;the-code&quot;&gt;The Code&lt;/h2&gt;
&lt;p&gt;In the situation we&amp;#39;re starting with, the function we&amp;#39;re using to draw the data points looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawPoints(
    points = pixelPoints.map { Offset(it.x + 20f, it.y) },
    pointMode = PointMode.Points,
    color = color,
    strokeWidth = 8.dp.toPx(),
    cap = StrokeCap.Round,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It takes the points and point mode from the parameters, and other than that, everything is defined inside the helper function. In this example, we&amp;#39;re interested in the &lt;code&gt;strokeWidth&lt;/code&gt; and &lt;code&gt;cap&lt;/code&gt;-properties because we&amp;#39;re using them to change how the data points are drawn. In addition, we will modify the code for drawing the path between the data points by adding an additional style. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s get started with changing the shape of the data points. First, let&amp;#39;s add new parameters for the &lt;code&gt;drawData&lt;/code&gt;-extension, which encapsulates the data points and line&amp;#39;s drawing methods:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun DrawScope.drawData(
    ...
    strokeWidth: Float = 8.dp.toPx(),
    strokeCap: StrokeCap = StrokeCap.Round
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We add two new parameters: &lt;code&gt;strokeWidth&lt;/code&gt; and &lt;code&gt;strokeCap,&lt;/code&gt; and then set the current values as defaults. Then we use these new properties in the code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawPoints(
    ...
    strokeWidth = strokeWidth,
    cap = strokeCap
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: We add these for both of the &lt;code&gt;drawPoints&lt;/code&gt;-methods, as there is one for highlighted points and one for all points. &lt;/p&gt;
&lt;p&gt;Then, in the &lt;code&gt;Graph&lt;/code&gt;-composable, we add the actual differentiation for each data set:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawData(
    ... // Engineering data
    strokeCap = StrokeCap.Round,
)
drawData(
    ... // ICT-data
    strokeCap = StrokeCap.Square,
)
drawData(
    ... // Total data
    strokeCap = StrokeCap.Butt,
    strokeWidth = 12.dp.toPx(),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we set the defaults, we don&amp;#39;t need to pass a value for both parameters if the value is the default value. That&amp;#39;s the case for the ICT and Engineering data, as we want to keep the point size the same. Actually, we wouldn&amp;#39;t need to pass the &lt;code&gt;strokeCap&lt;/code&gt;-value for the Engineering data either for the same reason - but I chose to add it for clarity and to display the different values. &lt;/p&gt;
&lt;p&gt;Okay, after these changes, the points look different. For Engineering data, nothing has changed, but for ICT data and total data, the points are rectangular, and the total data has more significant points than the others. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s add another differentiator before we look at the results. We want also to be able to distinguish the lines from each other, and one way to do that is by changing the pattern of the line. In this example, we will add a dashed line for one of the data sets. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s start with adding another new parameter for &lt;code&gt;drawData&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun DrawScope.drawData(
    dashed: Boolean = false,
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new parameter is &lt;code&gt;dashed,&lt;/code&gt; and it&amp;#39;s set to false by default. When it&amp;#39;s true, we want to change the style of the path that we&amp;#39;re drawing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawPath(
    path = path,
    color = color,
    style = Stroke(
        width = 3f,
        pathEffect = if (dashed) 
            PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f) 
        else null
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We add the&lt;code&gt;pathEffect&lt;/code&gt; for the stroke and set it to a dashed path effect with intervals of &lt;code&gt;10f&lt;/code&gt;. If &lt;code&gt;dashed&lt;/code&gt; is false, then we don&amp;#39;t set the path effect - or we set it to null, which is its default value.&lt;/p&gt;
&lt;p&gt;The last thing to do is to pass the value to one of the &lt;code&gt;drawData&lt;/code&gt;-functions, and in this case, we pass it to the ICT data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawData(
    ... // ICT-data
    strokeCap = StrokeCap.Square,
    dashed = true
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with that, we have added both shapes and a pattern. The final implementation looks like this:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/6SeTD2Z7UWsgaVy2ChQBEz/0d11d79ee14d6ee6be7fe7cf5cb394ea/Screenshot_20230813_070914.png&quot; alt=&quot;The lines have now different point-styles and one of the datasets has a dashed line.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/66f3744cc12c84480e0ba4ab6e85130d41d379aa&quot;&gt;You can find the final code from this commit.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we have looked at improving differentiating data by other means than color. We&amp;#39;ve covered adding shapes and patterns by changing the data points in the line chart to have different shapes and adding a dashed pattern to one of the lines. &lt;/p&gt;
&lt;p&gt;The fourth post in this series will cover switch devices and how navigation should work with them. Publishing it will take some time - I&amp;#39;ll need to find out why it wasn&amp;#39;t working with the solution it should have. &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-31/more-accessible-graphs-with-jetpack-compose-part-2-adding-keyboard-interaction/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 2: Adding Keyboard Interaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/66f3744cc12c84480e0ba4ab6e85130d41d379aa&quot;&gt;You can find the final code from this commit.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Understanding Density-Independent Pixels</title>
    <link href="https://eevis.codes/blog/2023-09-04/understanding-density-independent-pixels/" />
    <updated>2023-09-04T03:35:42.696Z</updated>
    <id>https://eevis.codes/blog/2023-09-04/understanding-density-independent-pixels/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1u83mziTsTr84FjOzEJY4m/08a2c2a2835573c224baa4791d1f2576/Understanding_Density-Independent_Pixels.png"/>]]>
      &lt;p&gt;I switched to Android development from web front end, and one thing that was a bit hard to understand was how the density of Android screens affects the use of units. For web, I mainly worked with pixels, &lt;code&gt;em&lt;/code&gt;s (the computed value of the element&amp;#39;s font size), and &lt;code&gt;rem&lt;/code&gt;s (the computed value of the root element&amp;#39;s font size). &lt;/p&gt;
&lt;p&gt;So, I wanted to write a blog post about this theme because I bet I&amp;#39;m not the only one. Let&amp;#39;s first look at the density-independent pixels, what they are, and why they&amp;#39;re used. After that, let&amp;#39;s discuss the conversion between them and pixels. I&amp;#39;ve purposely omitted scale-independent pixels from this blog post to concentrate on density-independent pixels.&lt;/p&gt;
&lt;h2 id=&quot;what-are-density-independent-pixels&quot;&gt;What Are Density-Independent Pixels?&lt;/h2&gt;
&lt;p&gt;Android devices have different sizes of screens. They also have different pixel sizes for a screen - so a certain amount of pixels in, e.g., width, looks different on a screen with fewer pixels compared to one with a lot more pixels. This is because one screen might fit only 320 pixels in the same physical space while the other fits 640 pixels. &lt;/p&gt;
&lt;p&gt;Density-independent pixels are here to fix the issues these differences cause. Android documentation defines density-independent pixels in the following way:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To preserve the visible size of your UI on screens with different densities, design your UI using density-independent pixels (dp) as your unit of measurement. One dp is a virtual pixel unit that&amp;#39;s roughly equal to one pixel on a medium-density screen (160 dpi, or the &amp;quot;baseline&amp;quot; density). Android translates this value to the appropriate number of real pixels for each other density.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://developer.android.com/training/multiscreen/screendensities&quot;&gt;Support different pixel densities - Android Developers&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s next look at the conversions between real pixels and density-independent pixels.&lt;/p&gt;
&lt;h2 id=&quot;conversions-between-pixels-and-density-independent-pixels&quot;&gt;Conversions Between Pixels and Density-Independent Pixels&lt;/h2&gt;
&lt;p&gt;There are multiple cases when we need the conversion from device-independent pixels to pixels - and vice versa. Let&amp;#39;s look at how we can convert them - first in the scope of Composable components and then in other cases.&lt;/p&gt;
&lt;h3 id=&quot;composable-components&quot;&gt;Composable Components&lt;/h3&gt;
&lt;p&gt;With Jetpack Compose, &lt;code&gt;LocalDensity&lt;/code&gt; helps with conversions between pixels and density-independent pixels. &lt;code&gt;LocalDensity&lt;/code&gt; is one of the built-in composition locals for Compose, and it provides a &lt;code&gt;Density&lt;/code&gt; that can be used for conversions between pixels and density-independent pixels, as well as scale-independent pixels.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s look at how we can use it in a Composable component. First, conversion from pixels to device-independent pixels:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun PxToDp(widthInPx: Int) {
  val density = LocalDensity.current
  val widthInDp = with (density) { widthInPx.toDp() }
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then vice versa:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun DpToPx(widthInDp: Int) {
  val density = LocalDensity.current
  val widthInPx = with (density) { widthInDp.toPx() }
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both of the examples utilize Kotlin&amp;#39;s &lt;code&gt;with&lt;/code&gt;-scope function. In the scope of &lt;code&gt;Density&lt;/code&gt; provided by &lt;code&gt;LocalDensity,&lt;/code&gt; the extension functions for conversion are available, and we can use them. &lt;/p&gt;
&lt;h3 id=&quot;other-cases&quot;&gt;Other Cases&lt;/h3&gt;
&lt;p&gt;When not using Jetpack Compose and/or &lt;code&gt;LocalDensity&lt;/code&gt; is unavailable, you can, for example, write your own extension functions for conversion. Here&amp;#39;s an example with extension functions for &lt;code&gt;Float&lt;/code&gt;-datatype:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun Float.toDp() = 
    (this / Resources.getSystem().displayMetrics.density)

fun Float.toPx() = 
    (this * Resources.getSystem().displayMetrics.density)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In both functions, the device&amp;#39;s screen density is fetched from &lt;code&gt;Resources.getSystem().displayMetrics.density&lt;/code&gt;. For the pixel-to-dp-conversion, a pixel value is divided by it, and for the dp-to-pixel-conversion, the dp value is multiplied by it. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve discussed device-independent pixels. We also looked at how to convert them to pixels and back. These conversions can be useful in many cases, such as when positioning elements on layouts. &lt;/p&gt;
&lt;p&gt;Do you have any examples of when converting between them was needed? Or when it was more complex than you would have wished? Share your thoughts!&lt;/p&gt;
&lt;h2 id=&quot;read-more&quot;&gt;Read More&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/training/multiscreen/screendensities&quot;&gt;Support different pixel densities - Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Density&quot;&gt;Density - Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>CatGPT - or How to Position Elements on Overlays</title>
    <link href="https://eevis.codes/blog/2023-10-22/catgpt-or-how-to-position-elements-on-overlays/" />
    <updated>2023-10-22T03:48:20.410Z</updated>
    <id>https://eevis.codes/blog/2023-10-22/catgpt-or-how-to-position-elements-on-overlays/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/6GLnUvTcOMm9GxN46GBFEF/d0a335e59695bb51e74e1186a2d1643b/catgpt__1_.png"/>]]>
      &lt;p&gt;Have you ever wondered how to display an element with an overlay while keeping it in the same position? The same way that, for example, many messaging apps highlight the message you want to react to. &lt;/p&gt;
&lt;p&gt;I had this type of challenge in one of my work projects. It took some trial and error to get it working, but I was so proud of myself when I finally found the correct modifiers (and numbers). I want to share one way to do it with you in this blog post.&lt;/p&gt;
&lt;h2 id=&quot;the-example-app&quot;&gt;The Example App&lt;/h2&gt;
&lt;p&gt;Okay, I must say that I maybe, just maybe, got a little bit carried away with this example app. But it&amp;#39;s a CatGPT - a chat client where you can ask anything from a cat. Here&amp;#39;s a short video:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/FQOnmA67pZheN5VmCIBip/dd42501e072af4811367885f3bf95465/catnip-video.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;This app is simple; It doesn&amp;#39;t use any third-party APIs, so the cat in question lives in the code. The app has one Room database, where it stores messages, and it has one screen called &lt;code&gt;ChatScreen&lt;/code&gt; which displays messages.&lt;/p&gt;
&lt;p&gt;If you want to see the code, the starting point for the app is in the &lt;a href=&quot;https://github.com/eevajonnapanula/CatGPT/tree/starting-point&quot;&gt;&lt;code&gt;starting-point&lt;/code&gt;-branch&lt;/a&gt;, and the final code is in &lt;a href=&quot;https://github.com/eevajonnapanula/CatGPT&quot;&gt;CatGPT-repository&amp;#39;s main-branch&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;showing-reaction-with-message&quot;&gt;Showing Reaction with Message&lt;/h2&gt;
&lt;p&gt;To simplify this app, each message can have only one reaction. In the &lt;code&gt;Message&lt;/code&gt; data class, the reaction is already present: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// data/Message.kt

@Entity
data class Message(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val text: String = &amp;quot;&amp;quot;,
    val sender: Sender = Sender.ME,
    @TypeConverters(ReactionConverter::class)
    val reaction: Reaction? = null,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it&amp;#39;s a nullable Reaction-enum. The complete code and converters for Reaction are in &lt;a href=&quot;https://github.com/eevajonnapanula/CatGPT/blob/main/app/src/main/java/com/eevajonna/catgpt/data/Message.kt&quot;&gt;&lt;code&gt;Message.kt&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s first add code for showing that reaction before building the actual reaction picker. In the &lt;code&gt;MessageRow.kt&lt;/code&gt;-file, there is already a &lt;code&gt;ConstraintLayout&lt;/code&gt;, which contains the component for the message. We want to show the reaction in the bottom right corner of that component, so we&amp;#39;ll add the reaction inside the constraint layout. &lt;/p&gt;
&lt;p&gt;The component for reaction looks like this: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// ui/components/MessageRow.kt

message.reaction?.let {
    Text(
        modifier = Modifier
            .clip(ChatScreen.shape)
            .background(MaterialTheme.colorScheme.surface)
            .padding(MessageRow.reactionPadding)
        text = it.emoji,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As the reaction is a nullable string, we first check if it&amp;#39;s available with Kotlin&amp;#39;s &lt;code&gt;let&lt;/code&gt;, and inside its block, we add the reaction as &lt;code&gt;Text&lt;/code&gt;-element. &lt;/p&gt;
&lt;p&gt;Now,  the reaction is visible. We still want to position it correctly, so we&amp;#39;ll first add the reaction as a reference for constraint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// ui/components/MessageRow.kt

// From
val (text) = createRefs()
// To
val (text, reaction) = createRefs()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then for the &lt;code&gt;Text&lt;/code&gt;-component we just added, we&amp;#39;ll add the &lt;code&gt;constrainAs&lt;/code&gt;-modifier:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// ui/components/MessageRow.kt

message.reaction?.let {
    Text(
        modifier = Modifier
            ...
            .constrainAs(reaction) {
            end.linkTo(text.end, MessageRow.reactionPadding)
            bottom.linkTo(text.bottom)
            },
        text = it,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the &lt;code&gt;constrainAs&lt;/code&gt;-modifier, we constrain the end of the component to the end of the message text with a bit of padding and the bottom of the reaction to the bottom of the message text. After these changes, the message component with a reaction would look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2Hf2V8YxpCO4PKzS2C24tb/1782e841b70f1a15801a8a006c229aaf/Screenshot_2023-10-20_at_7.08.09.png&quot; alt=&quot;Text &amp;quot;Meow meow meow meow meow meow&amp;quot; on dark pink background. On the bottom right corner, there is a grinning cat with smiley eyes.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;All the &lt;a href=&quot;https://github.com/eevajonnapanula/CatGPT/commit/3213f53c2dec17250e65ee6db7938feee7dcd847&quot;&gt;changes for showing the reaction with the message are in this commit.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-reaction-picker&quot;&gt;The Reaction Picker&lt;/h2&gt;
&lt;p&gt;Alright. Now we&amp;#39;re showing the reactions - but to have something to display, there should be a way to add a reaction. We will do it with an overlay with a blurred background and a component that shows the selected message and available reactions.&lt;/p&gt;
&lt;h3 id=&quot;getting-the-messages-y-position&quot;&gt;Getting the Message&amp;#39;s Y-Position&lt;/h3&gt;
&lt;p&gt;We first want to get the y-position of the message component in the conversation to position the element correctly in the overlay we&amp;#39;re adding. We can do it by using the &lt;code&gt;onGloballyPositioned&lt;/code&gt;-modifier and storing the y-value to a state variable. For that, we also need to know the density of the user&amp;#39;s phone so that we can convert between the pixels and density-independent pixels:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MessageRow.kt

val density = LocalDensity.current
var yPosition by remember { mutableStateOf(0.dp) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&amp;#39;re wondering about the need for density or need a refresher on density-independent pixels, I&amp;#39;ve written a blog post about them: &lt;a href=&quot;https://eevis.codes/blog/2023-09-04/understanding-density-independent-pixels/&quot;&gt;Understanding Density-Independent Pixels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Okay, now we have what we need to get the actual position:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MessageRow.kt

ConstraintLayout(
    modifier = Modifier.
        .fillMaxWidth()
        .onGloballyPositioned {
            yPosition = with (density) { 
                it.positionInParent().y.toDp() +
                  MessageRow.reactionDialogOffset + 
                  MessageRow.reactionPadding
            }
        }
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use the &lt;code&gt;.onGloballyPositioned&lt;/code&gt;-modifier to get the y-position. &lt;code&gt;positionInParent()&lt;/code&gt; returns pixels, and to get the density-independent pixels for position, we use the &lt;code&gt;with&lt;/code&gt;-scope function to convert them. We also add a bit of offset and padding to match the position because the original message component has those offset and padding. &lt;/p&gt;
&lt;h3 id=&quot;reaction-picker-component&quot;&gt;Reaction Picker Component&lt;/h3&gt;
&lt;p&gt;After the changes in the previous section, we have the y-position of the message. The next thing we need to do is to create the reaction picker component and pass the y-position to that component. After that, we can actually place the element in the overlay. &lt;/p&gt;
&lt;p&gt;The component inside the overlay is a &lt;code&gt;ConstraintLayout&lt;/code&gt;, which wraps a &lt;code&gt;MessageBlock&lt;/code&gt; that contains a message text, and &lt;code&gt;Reactions&lt;/code&gt;, which displays the available reactions. It also has a &lt;code&gt;Box&lt;/code&gt; used as overlay/background, covering the whole screen. You can find the complete component from &lt;a href=&quot;https://github.com/eevajonnapanula/CatGPT/blob/main/app/src/main/java/com/eevajonna/catgpt/ui/components/ReactionPicker.kt&quot;&gt;&lt;code&gt;ReactionPicker.kt&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Okay, now we have the component, which takes the y-position of the message as a parameter. Let&amp;#39;s move on to placing the elements.&lt;/p&gt;
&lt;h3 id=&quot;placing-elements-on-the-screen&quot;&gt;Placing Elements on the Screen&lt;/h3&gt;
&lt;p&gt;To place the message block and reactions correctly within the screen, we use a custom modifier that takes the y-position of the message from the message screen and finds the correct position for the message and reactions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun Modifier.positionReactionsPicker(yPosition: Dp) = layout { measurable, constraints -&amp;gt;
    val placeable = measurable.measure(constraints)

    layout(placeable.width, placeable.height) {
        placeable.place(0, yPosition.roundToPx())
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To accomplish what we want, we use the &lt;code&gt;layout&lt;/code&gt;-modifier. It&amp;#39;s a lambda with two parameters: &lt;code&gt;measurable&lt;/code&gt; and &lt;code&gt;constraints.&lt;/code&gt; We first measure the element presented by the &lt;code&gt;measurable&lt;/code&gt; parameter (the component on which the custom modifier is called) and store the value in the &lt;code&gt;placeable&lt;/code&gt; variable. &lt;/p&gt;
&lt;p&gt;Then, we create the layout with measured height and width and place the element with &lt;code&gt;placeable.place()&lt;/code&gt;. For the x-value, we use &amp;quot;0&amp;quot; because the component fills the whole width, and we can place it on the left edge of the screen. For the y-value, we use the &lt;code&gt;yPosition&lt;/code&gt; passed to the modifier and round it to pixels. &lt;/p&gt;
&lt;p&gt;Finally, we call the custom modifier on the &lt;code&gt;ConstraintLayout&lt;/code&gt; that is wrapping the message and reactions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;ConstraintLayout(
    modifier = Modifier.positionReactionsPicker(yPosition)
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this way, we have positioned the reactions picker: &lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2son0NyrlhPY8OlBBeKBMa/02bc563227cbf2597fd9c637f58c6da3/Screenshot_20231020_071910.png&quot; alt=&quot;CatGPT-app with a message consisting of multiple &#39;meow&#39;s open. Above the message, there are four cat emojis: Grinning cat, grinning cat with smiling eyes, weary cat and smiling cat with heart eyes.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked at how to position elements on screen. We&amp;#39;ve done that by getting the y-position of that message and then creating an overlay where we&amp;#39;ve placed the opened message with the help of a custom layout modifier. &lt;/p&gt;
&lt;p&gt;This code is a good starting place visually but has some accessibility problems. Not everyone who uses different assistive technologies can use it, so I will write a second blog post tackling some of the accessibility issues this code has.&lt;/p&gt;
&lt;p&gt;Do you have any comments or questions? Please share and/or ask!&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>How to Add Content Descriptions in Compose  - A Guide for Android Devs</title>
    <link href="https://eevis.codes/blog/2023-11-15/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs/" />
    <updated>2023-11-15T04:09:07.227Z</updated>
    <id>https://eevis.codes/blog/2023-11-15/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/5aTib3hHBr7scU4fOFnrIR/d1d74df1017b61cfa3cfd71c6e44e123/content_desc__3_.png"/>]]>
      &lt;p&gt;When it comes to the &lt;code&gt;contentDescription&lt;/code&gt;-attribute, I&amp;#39;ve noticed a couple of things Android devs do that are wrong and make apps harder to use with assistive technology. The first thing I see a lot is setting the content description of an icon to the icon name, such as &lt;code&gt;icon_cat_sleeping.&lt;/code&gt; Another thing is suppressing the warnings Android Studio gives about missing content descriptions. &lt;/p&gt;
&lt;p&gt;I decided to write about this theme, and in this blog post, you&amp;#39;ll learn about content descriptions: the problems and solutions. And yes, you&amp;#39;ll understand why those things I mentioned in the previous paragraph are problematic.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s start discussing content description, then continue with how to write one, and finally, how to set the content description.&lt;/p&gt;
&lt;h2 id=&quot;what-content-description-is&quot;&gt;What Content Description Is&lt;/h2&gt;
&lt;p&gt;When I talk about content description, I mean text alternative. If you have any background in web development or, e.g., using any social media platforms, you might have heard about the word &amp;quot;alt text&amp;quot;. They mean the same thing and different environments have different names for this concept. &lt;/p&gt;
&lt;p&gt;In Android, the attribute is called &lt;code&gt;contentDescription,&lt;/code&gt; and I think that content description is actually a very explanatory name for what we&amp;#39;re trying to accomplish with it: describing the content of an image or a graphic. &lt;/p&gt;
&lt;p&gt;One thing that&amp;#39;s important to remember is who we are describing the content for: Users who utilize the accessibility APIs. This means, for example, screen reader and voice access users. Screen reader users rely on the content descriptions for visual content (that they usually can&amp;#39;t see, depending on their sight level), and voice access users can use these values as labels to interact with elements, such as buttons. &lt;/p&gt;
&lt;p&gt;Content descriptions should be added for meaningful visual elements like images, icons, and graphs. For some of the elements, it&amp;#39;s more straightforward, but for, for example, different types of graphs, it&amp;#39;s more complicated. This blog post won&amp;#39;t go into details on adding content descriptions for graphs, but I&amp;#39;ve written a blog post as one example around the theme if you&amp;#39;re interested: &lt;a href=&quot;https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Now, if you&amp;#39;ve been working with testing tools such as Appium, you might remember adding the test id as &lt;code&gt;contentDescription.&lt;/code&gt; Unfortunately, some tools use the so-called &amp;quot;accessibility id,&amp;quot; which maps to &lt;code&gt;contentDescription&lt;/code&gt; on Android. On iOS, there is a separate property called &lt;code&gt;accessibilityId&lt;/code&gt;. This is a problem because content description actually overrides the text value for many elements, meaning that for these elements, a screen reader user would hear the value of a button being &lt;code&gt;button_submit&lt;/code&gt; (the id set for testing) instead of the &amp;quot;Submit&amp;quot; that&amp;#39;s a text for that button.&lt;/p&gt;
&lt;p&gt;I&amp;#39;ve had to fix issues like this, and my solution was to check that if a screen reader is running, we would set the element&amp;#39;s content as &lt;code&gt;contentDescription&lt;/code&gt;; otherwise, use that testing id. That is not an optimal solution, but it was the best we could do at the time. And this would fail with some of the automated accessibility checks, even if it works for real screen reader users.&lt;/p&gt;
&lt;h2 id=&quot;content-description-is-not-available-for-all-users&quot;&gt;Content Description is not Available for All Users&lt;/h2&gt;
&lt;p&gt;The content description is invisible to most users. This means that it&amp;#39;s not the all-powerful solution to fix accessibility issues. &lt;/p&gt;
&lt;p&gt;A common misconception is that adding it as a label to an icon would make it available to every user. But that&amp;#39;s not the case - a user who is not using any sort of assistive technology can&amp;#39;t see it, nor can someone with, for example, a keyboard. So, it&amp;#39;s available just for assistive technologies that utilize the accessibility API underneath - including, but not limited to, screen readers, switch access, and voice navigation.&lt;/p&gt;
&lt;p&gt;I want to underline this because many users usually would benefit from things like text labels with icons, but they tend to be dismissed when asking for them because of the abovementioned misconception. I&amp;#39;m one of them, so I&amp;#39;ve experienced this firsthand. &lt;/p&gt;
&lt;h2 id=&quot;how-to-write-a-good-content-description&quot;&gt;How to Write a Good Content Description&lt;/h2&gt;
&lt;p&gt;The actual content for the &lt;code&gt;contentDescription&lt;/code&gt; attribute varies, depending on the purpose and type of the element. As mentioned, more complex visualizations are outside the scope of this blog post, so we will discuss meaningful and decorative images. We&amp;#39;ll also discuss icons as a separate, unique use case. &lt;/p&gt;
&lt;p&gt;One important thing to note is that whenever you write a content description, remember to localize it! &lt;/p&gt;
&lt;h3 id=&quot;meaningful-images&quot;&gt;Meaningful Images&lt;/h3&gt;
&lt;p&gt;What does a &amp;quot;meaningful image&amp;quot; mean? It means any image (or graphic) that has meaning and adds something to the content. It includes icons, and pictures that contain instructions is another good example. The text alternative (so, content description) should contain what the image is trying to communicate. &lt;/p&gt;
&lt;p&gt;An image&amp;#39;s content description is also affected by the context where the image is. WebAIM has a list of instructions for writing text alternatives:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The alt attribute should &lt;strong&gt;typically&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;be accurate and equivalent&lt;/strong&gt; in representing content and function.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;be succinct&lt;/strong&gt;. Content (if any) and function (if any) should be presented as succinctly as possible, without sacrificing accuracy. Typically, only a few words are necessary, though rarely a short sentence or two may be appropriate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;not be redundant&lt;/strong&gt; or provide the same information as text near the image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;not include phrases like &amp;quot;image of ...&amp;quot; or &amp;quot;graphic of ...&amp;quot;, etc.&lt;/strong&gt; This would be redundant since screen readers already announce &amp;quot;graphic&amp;quot; along with the alt text. If the fact that an image is a photograph or illustration, etc. is important content, it may be useful to include this in alternative text.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Source and read more: &lt;a href=&quot;https://webaim.org/techniques/alttext/&quot;&gt;WebAIM: Alternative text&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;decorative-images&quot;&gt;Decorative Images&lt;/h3&gt;
&lt;p&gt;In the previous section, we&amp;#39;ve talked about meaningful images. Sometimes, illustrations or images are there for purely decorative purposes and don&amp;#39;t have any other meaning. In these cases, you should mark the content description as null:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Image(
  ...
  contentDescription=null
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you might wonder, why not leave it as an empty string? The reason is that when left empty, the accessibility API doesn&amp;#39;t get the info that this is decorative and keeps this image in the accessibility tree. This leads to a situation where, when a user encounters that image, they would hear &amp;quot;Unnamed&amp;quot; instead of not encountering it in the first place. &lt;/p&gt;
&lt;p&gt;That &amp;quot;Unnamed&amp;quot; is redundant information and can be really confusing. Is that image really decorative, and a developer didn&amp;#39;t know how to set it as such? Or is it meaningful, but the developer forgot to add the actual text alternative? &lt;/p&gt;
&lt;p&gt;In the Compose world, components like &lt;code&gt;Image&lt;/code&gt; and &lt;code&gt;Icon&lt;/code&gt; require &lt;code&gt;contentDescription&lt;/code&gt; to be set explicitly. However, it was (or, is) possible to leave the attribute out in the View world and XML-layouts. &lt;/p&gt;
&lt;p&gt;That would lead to similar situations, with &amp;quot;Unnamed&amp;quot; being announced. This would also create a warning in the Android Studio about missing content descriptions. It&amp;#39;s possible to suppress that warning, and I&amp;#39;ve fixed multiple instances where the warning was suppressed. &lt;/p&gt;
&lt;p&gt;So, don&amp;#39;t suppress that warning - set the content description to &lt;code&gt;null&lt;/code&gt; or provide a descriptive content description, depending on the image.&lt;/p&gt;
&lt;h3 id=&quot;icons&quot;&gt;Icons&lt;/h3&gt;
&lt;p&gt;As mentioned, icons are a special case. Icons are often used as the only action indicator (like an edit button with just some icon representing editing). I usually have problems with this because of how I process information. I keep asking for text labels but typically don&amp;#39;t get them. But this is not a blog post about text labels; it&amp;#39;s about content descriptions, so let&amp;#39;s move on.&lt;/p&gt;
&lt;p&gt;When an icon is the only thing inside a button, it&amp;#39;s even more critical to include meaningful content descriptions because it&amp;#39;s the text used for the button. So, when a screen reader user encounters a button with an icon that is a quill, they&amp;#39;d hear something like &amp;quot;Button, Edit&amp;quot; instead of &amp;quot;Button, icon underscore quill&amp;quot; if the content description is set to the icon&amp;#39;s name. Or, in the case I mentioned in the intro, &amp;quot;Icon underscore cat underscore sleeping.&amp;quot; Remember that whatever text you enter there will be what non-sighted screen reader users hear (or read from braille) - they can&amp;#39;t see the icon.&lt;/p&gt;
&lt;p&gt;So, when adding a content description for an icon, ask yourself if it&amp;#39;s used to convey meaning or if it&amp;#39;s purely decorative. If it&amp;#39;s the latter, use &lt;code&gt;null&lt;/code&gt;. Otherwise, describe the action or meaning, not the actual icon. So, usually, an &amp;quot;X&amp;quot; icon represents something like &amp;quot;Close,&amp;quot; and a chevron pointing to the left means something like &amp;quot;Back&amp;quot;. &lt;/p&gt;
&lt;h2 id=&quot;how-to-set-the-content-description&quot;&gt;How to Set the Content Description&lt;/h2&gt;
&lt;h3 id=&quot;contentdescription-attribute&quot;&gt;&lt;code&gt;contentDescription&lt;/code&gt;-attribute&lt;/h3&gt;
&lt;p&gt;You can set the content description through the &lt;code&gt;contentDescription&lt;/code&gt;-attribute for &lt;code&gt;Image&lt;/code&gt; and &lt;code&gt;Icon&lt;/code&gt; -components. Other components don&amp;#39;t have this attribute, so you&amp;#39;ll need the help of &lt;code&gt;semantics&lt;/code&gt; or &lt;code&gt;clearAndSetSemantics&lt;/code&gt;-modifiers. &lt;/p&gt;
&lt;h3 id=&quot;semantics-and-clearandsetsemantics-modifiers&quot;&gt;&lt;code&gt;semantics&lt;/code&gt; and &lt;code&gt;clearAndSetSemantics&lt;/code&gt;-modifiers&lt;/h3&gt;
&lt;p&gt;Both &lt;code&gt;semantics&lt;/code&gt; and &lt;code&gt;clearAndSetSemantics&lt;/code&gt; modifiers change the element&amp;#39;s semantics. The difference is that the first builds on top of existing semantics, and the latter clears all the other semantics.&lt;/p&gt;
&lt;p&gt;While &lt;code&gt;Image&lt;/code&gt; and &lt;code&gt;Icon&lt;/code&gt; components have the &lt;code&gt;contentDescription,&lt;/code&gt; other elements, such as graphics created with &lt;code&gt;Canvas,&lt;/code&gt; would require using these modifiers. Also another case is text with an emoji - for example, if your app is a chat app with reactions, and you&amp;#39;re using &lt;code&gt;Text&lt;/code&gt;-element for displaying the reaction, you&amp;#39;d need to set the content description for those &lt;code&gt;Text&lt;/code&gt; components. &lt;/p&gt;
&lt;p&gt;You can use the &lt;code&gt;semantics&lt;/code&gt;-modifier in any element and set the content description within its semantic properties lambda:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Canvas(
    modifier = Modifier.semantics {
        contentDescription = &amp;quot;Put the content description here&amp;quot;
    }
) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve covered content descriptions, what they are, how to write them, and how you can set them. &lt;/p&gt;
&lt;p&gt;Do you have questions? Or comments? Please do ask or share!&lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webaim.org/techniques/alttext/&quot;&gt;WebAIM: Alternative text&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Women Developer Academy Europe 2023 - My Experience</title>
    <link href="https://eevis.codes/blog/2023-11-24/women-developer-academy-europe-2023-my-experience/" />
    <updated>2023-11-24T03:44:15.259Z</updated>
    <id>https://eevis.codes/blog/2023-11-24/women-developer-academy-europe-2023-my-experience/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/73mAAQQvnfBULhthuQPti0/e78d63361fe8a38104ff4faf1d24558e/WDA.png"/>]]>
      &lt;p&gt;My October was busy. I spoke at three conferences (droidcon Italy, droidcon London, and KKON, which was an online conference). I had also applied to the Women Developer Academy Europe earlier, and when the acceptance letter arrived at the end of September, I wasn&amp;#39;t sure if I could participate. But as you might guess from me writing this blog post, I decided to attend, and it was a great decision! In this blog post, I&amp;#39;ll share my experiences with the program. &lt;/p&gt;
&lt;h2 id=&quot;women-developer-academy-wda&quot;&gt;Women Developer Academy (WDA)&lt;/h2&gt;
&lt;p&gt;Women Developer Academy is a program from Google, and &lt;a href=&quot;https://rsvp.withgoogle.com/events/women-developer-academy-europe/&quot;&gt;the event page for our cohort (Women Developer Academy Europe)&lt;/a&gt; explains it in a nutshell:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Women Developer Academy is a specialized program for female developers, aimed at increasing the diversity of speakers at tech events in the region, focusing on creating an inclusive community of speakers and mentors who can support each other. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The program consisted of weekly sessions, the I Am Remarkable workshop, mentoring, and a mini-conference with 5-minute talks. I&amp;#39;ll share a bit more about them next.&lt;/p&gt;
&lt;h2 id=&quot;sessions&quot;&gt;Sessions&lt;/h2&gt;
&lt;p&gt;The program had 2-3 evening sessions each week. The topics for the meetings were about public speaking, applying to conferences, online branding, and many other related topics. Many of those topics were familiar, as I&amp;#39;ve spoken at many conferences, but I also learned new things.&lt;/p&gt;
&lt;p&gt;For me, living in the eastern side of Europe in Finland, these meetings started at seven in the evening. It&amp;#39;s not an optimal time for someone who is definitely a morning person, and needs their wind-down time in the evening. But I was so happy that we got the sessions as recordings and could watch them later! &lt;/p&gt;
&lt;h2 id=&quot;i-am-remarkable-workshop&quot;&gt;I Am Remarkable-workshop&lt;/h2&gt;
&lt;p&gt;The WDA program included a &lt;a href=&quot;https://rmrkblty.org/&quot;&gt;I Am Remarkable workshop&lt;/a&gt;. The website describes the movement behind this workshop:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;#IAmRemarkable is a global movement that empowers everyone to celebrate their achievements in the workplace and beyond.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;#39;ve heard of this workshop but never got to attend one. I can see the need for such workshops - especially within minority groups in tech. It&amp;#39;s so easy to forget or not see the accomplishments, and this workshop is a great time to dedicate some time to acknowledging and celebrating them.&lt;/p&gt;
&lt;p&gt;We spent one Saturday afternoon talking about ways we are remarkable, and how self-promotion matters. I can share a couple of sentences I wrote during the workshop:&lt;/p&gt;
&lt;p&gt;I am remarkable because I care and know a lot about accessibility.&lt;/p&gt;
&lt;p&gt;I am remarkable because I dare to question inequality.&lt;/p&gt;
&lt;p&gt;I am remarkable because I&amp;#39;m really good at what I do. &lt;/p&gt;
&lt;h2 id=&quot;mentoring&quot;&gt;Mentoring&lt;/h2&gt;
&lt;p&gt;The third part of the program was mentoring. The minimum number of mentoring sessions to graduate from the program was 4. To be honest, I didn&amp;#39;t have time to even think of having more mentoring sessions. I wish I had more time, but it is what it is.&lt;/p&gt;
&lt;p&gt;I had four interesting mentoring sessions, and we discussed many different things related to accessibility, speaking at conferences, and the GDE program, to name a few. A huge thanks to Piotr Prus, Sabrina Jodexnis, Nourhan Gehad, and Carlos Mota!&lt;/p&gt;
&lt;h2 id=&quot;mini-conference&quot;&gt;Mini-Conference&lt;/h2&gt;
&lt;p&gt;In the last session before graduation from the program, we had a mini-conference where everyone could talk for 5 minutes about a topic they&amp;#39;re interested in. The timing wasn&amp;#39;t optimal for me - I had a flight to London the next day, but luckily, I got to present my talk second in order. &lt;/p&gt;
&lt;p&gt;I spoke about Android developers and the challenges we face regarding accessibility. The talk was based on a blog post I&amp;#39;ve written: &lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/&quot;&gt;Android Developers and Accessibility - Challenges and Proposed Solutions&lt;/a&gt;. Five minutes is a really short time, and the amount of content was almost too much for that time. I had to skip introductions to fit everything in.&lt;/p&gt;
&lt;p&gt;I got good feedback, but it was interesting that some people said they liked my slides that had almost nothing on them - and some said that I should add, e.g., pictures. It is an excellent example of how we all are different - some like more simplistic slides, and others want more visual ones.&lt;/p&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Despite having a really busy October, I am happy I got accepted into WDA and decided to participate. I met amazing people, made connections, and learned new things. &lt;/p&gt;
&lt;p&gt;So, if you are in the target group of this program, and whenever it opens in your area, I highly recommend applying!&lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://rsvp.withgoogle.com/events/women-developer-academy-europe/&quot;&gt;The event page for our cohort (Women Developer Academy Europe)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rmrkblty.org/&quot;&gt;I Am Remarkable workshop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-17/android-developers-and-accessibility-challenges-and-proposed-solutions/&quot;&gt;Android Developers and Accessibility - Challenges and Proposed Solutions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>More Accessible Graphs with Jetpack Compose Part 4: On-Screen Control Buttons</title>
    <link href="https://eevis.codes/blog/2023-12-09/more-accessible-graphs-with-jetpack-compose-part-4-on-screen-control-buttons/" />
    <updated>2023-12-09T10:34:40.289Z</updated>
    <id>https://eevis.codes/blog/2023-12-09/more-accessible-graphs-with-jetpack-compose-part-4-on-screen-control-buttons/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/bnonU2dSCPHLgUev8MgMK/be2f085ea6f3dc01ab7ea802255a979e/more-accessible-graphs-4__1_.png"/>]]>
      &lt;p&gt;This blog post is the fourth one in my series on more accessible graphs with Jetpack Compose. You can find the previous three from the following links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-31/more-accessible-graphs-with-jetpack-compose-part-2-adding-keyboard-interaction/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 2: Adding Keyboard Interaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-08-16/more-accessible-graphs-with-jetpack-compose-part-3-differentiating-without/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 3: Differentiating without Color&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Continuous (or path-based) pointer input, like drag-gesture, might be problematic for some users. For example, if a user has tremors in their hands, the gesture is not always continuous, and if the app relies on this kind of gesture to work, it may be unusable for these users.&lt;/p&gt;
&lt;p&gt;Now, if you have followed this blog post series, you might wonder - hey, we added keyboard interaction; isn&amp;#39;t that enough? No, it&amp;#39;s not - it would require a physical keyboard. Many users don&amp;#39;t use a physical keyboard, even if they could benefit from it. &lt;/p&gt;
&lt;p&gt;So, in this blog post, we&amp;#39;ll add on-screen controls to the graph. The best part is that this solution also solves the issues with a switch device I mentioned at the end of the blog post about keyboard interaction! Head to the paragraph about &lt;a href=&quot;https://eevis.codes/blog/2023-12-09/more-accessible-graphs-with-jetpack-compose-part-4-on-screen-control-buttons/#switch-access&quot;&gt;Switch Access&lt;/a&gt; for more in-depth explanations. &lt;/p&gt;
&lt;h2 id=&quot;adding-buttons&quot;&gt;Adding Buttons&lt;/h2&gt;
&lt;p&gt;So, one way to solve this problem with path-based gestures is to add visible buttons as an alternative way of navigating inside the graph. In the case of this graph, we want to add two buttons: One for going forward and another for going backward. This is what the UI will look like once we add the controls:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2MilAAL2neGpVhLZ1Ok8c8/021955c6ac95a3a0083532bfe2dbb86b/Screenshot_20231208_071934.png&quot; alt=&quot;Graph example app&#39;s UI, with newly added Previous year and Next year-buttons under the graph.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;We add a new component, called &lt;code&gt;ControlButtons&lt;/code&gt; that wraps these buttons: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun ControlButtons(
    highlightedX: Float?,
    lastIndex: Int,
    setFocus: (Int) -&amp;gt; Unit
) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It takes three parameters: &lt;code&gt;highlightedX&lt;/code&gt;, which is the currently highlighted year on the x-axis, &lt;code&gt;lastIndex&lt;/code&gt;, which is the last year&amp;#39;s index, and a function called &lt;code&gt;setFocus&lt;/code&gt;, which takes in an integer - the index where the focus should go next - and returns &lt;code&gt;Unit&lt;/code&gt;. This is what it looks like when we call it in the parent component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;ControlButtons(
    highlightedX = highlightedX,
    lastIndex = pixelPointsForTotal.count() - 1,
) { selectedIndex -&amp;gt;
    highlightedX = pixelPointsForTotal[selectedIndex].x
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, we pass in the &lt;code&gt;highlightedX&lt;/code&gt; that we have been using to define the currently highlighted year, the last index for pixel point lists, and then, in the lambda block, we take in the new selected index and set the &lt;code&gt;highlightedX&lt;/code&gt; to the x-value in the pixel point list in that index.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s get back to the &lt;code&gt;ControlButtons&lt;/code&gt; composable. At the beginning of the composable, we&amp;#39;ll define two things:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// ControlButtons

var selectedIndex by remember {
    mutableStateOf(-1)
}

fun setSelectedIndexAndFocus(newIndex: Int) {
    selectedIndex = newIndex
    setFocus(newIndex)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, we want to store the currently selected index to a &lt;code&gt;selectedIndex&lt;/code&gt; variable, remember it and set the initial value to -1 as there is no index selected initially. We&amp;#39;ll also want a function to set a selected index and focus to update the state within the component, and set the new highlighted year (or, x) on the parent component, as we saw a couple of paragraphs ago. We call it &lt;code&gt;setSelectedIndexAndFocus&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;After that, we&amp;#39;ll have everything we need in that component to create the actual buttons. As they&amp;#39;re pretty similar, we&amp;#39;ll make a new component called &lt;code&gt;ControlButton&lt;/code&gt; that takes in type (we&amp;#39;ll talk about it a bit later), currently selected index, last index, first focus index (so, where the focus should go first when pressing this button if nothing is selected), highlighted X value, and a function to set the focus:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun ControlButton(
    type: GraphControlType,
    currentIndex: Int,
    lastIndex: Int,
    firstFocusIndex: Int,
    highlightedX: Float?,
    setFocus: (Int) -&amp;gt; Unit,
) {
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make the function a bit more abstract in order to work for both the next and previous years, I made an enum called &lt;code&gt;GraphControlType&lt;/code&gt;: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;enum class GraphControlType {
    Next {
        override val icon = Icons.Filled.ArrowForward
        override val textResId = R.string.button_next_year
    },
    Previous {
        override val icon = Icons.Filled.ArrowBack
        override val textResId = R.string.button_previous_year
    }, ;

    abstract val icon: ImageVector
    abstract val textResId: Int
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This enum contains the icon we want to use and the resource id for the text in the button. &lt;/p&gt;
&lt;p&gt;As mentioned, the idea of the button is that it moves the focus to the next year when pressed. If it&amp;#39;s the &amp;quot;next year&amp;quot; button, it would go forward from last year to the first one. If it&amp;#39;s the &amp;quot;previous year&amp;quot; button, it would go backward; when it&amp;#39;s in the first year, it would go to the last year.&lt;/p&gt;
&lt;p&gt;We have helper functions for getting the next index (or, year) to focus (you can see them in the &lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/0b049a57c89a1fc73f0224c0f1e4130871f63ae9&quot;&gt;commit containing all the changes&lt;/a&gt;), and we want to define which one to use, depending on the type that we passed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// ControlButton

val nextIndexFunc = when (type) {
    GraphControlType.Next -&amp;gt; ::getNextIndex
    GraphControlType.Previous -&amp;gt; ::getPreviousIndex
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we&amp;#39;ve defined everything, we&amp;#39;ll add the button:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// ControlButton

OutlinedButton(onClick = {
    val newSelectedIndex = when (highlightedX) {
        null -&amp;gt; firstFocusIndex
        else -&amp;gt; nextIndexFunc(currentIndex, lastIndex)
    }

    setFocus(newSelectedIndex)
}) {
    if (type == GraphControlType.Previous) 
        ControlButtonIcon(icon = type.icon)
    Text(stringResource(id = type.textResId))
    if (type == GraphControlType.Next) 
        ControlButtonIcon(icon = type.icon)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, in the &lt;code&gt;onClick&lt;/code&gt; handler of the button, we check if the highlighted x-value is null, and if it is, then we move the focus to the first focus index, which is the first index for the &amp;quot;next year&amp;quot; button, and the last index for the &amp;quot;previous year&amp;quot; button. Otherwise, we&amp;#39;ll use the defined &lt;code&gt;nextIndexFunc&lt;/code&gt; to get the next index where the focus should go. Once we have the index, we call the &lt;code&gt;setFocus&lt;/code&gt;-function with that index.&lt;/p&gt;
&lt;p&gt;In the content of the button, we use the type to define the placement of the icon - for the &amp;quot;previous year&amp;quot; button, it&amp;#39;s before the text, and for the &amp;quot;next year&amp;quot; button, it&amp;#39;s after the text.&lt;/p&gt;
&lt;p&gt;The final thing is to add the controls to the &lt;code&gt;ControlButtons&lt;/code&gt;-composable. We want them to be on the same row, so we wrap them with the &lt;code&gt;Row&lt;/code&gt;-component. I&amp;#39;ve left out some modifiers for clarity:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// ControlButtons

Row(...) {
    ControlButton(
        type = GraphControlType.Previous,
        currentIndex = selectedIndex,
        lastIndex = lastIndex,
        firstFocusIndex = lastIndex,
        highlightedX = highlightedX,
        setFocus = {
            setSelectedIndexAndFocus(it)
        },
    )
    ControlButton(
        type = GraphControlType.Next,
        currentIndex = selectedIndex,
        lastIndex = lastIndex,
        firstFocusIndex = 0,
        highlightedX = highlightedX,
        setFocus = {
            setSelectedIndexAndFocus(it)
        },
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, now we have added the controls. Here&amp;#39;s a video of how they work:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/4XpsFYQJKccoUFDRh8C1a1/927cecd6256905816651b3cd0e7d2561/Screen_recording_20231208_072026.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/0b049a57c89a1fc73f0224c0f1e4130871f63ae9&quot;&gt;You can find the final code from this commit.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;switch-access&quot;&gt;Switch Access&lt;/h2&gt;
&lt;p&gt;The initial implementation for keyboard access did not work on a switch device. When I was testing it with the switch, I noticed that the focusable elements are not available to the API a switch device uses, so the implementation did not work. I don&amp;#39;t know why this happens; I haven&amp;#39;t had time to investigate it further, but if anyone knows more about the technical details behind this behavior, I&amp;#39;d be glad to learn more!&lt;/p&gt;
&lt;p&gt;This is also an excellent example of why you must test with different assistive technologies. Assuming that something works without testing might lead to some inaccessible code. &lt;/p&gt;
&lt;p&gt;So, you might wonder what the app now looks like for a switch device user. I recorded a short video: &lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/L7625w3b2Ck4srPptAHFS/d64abb6fff34069a30700bc3d37aef71/Screen_recording_20231208_072144.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;As you can see in the video, adding these buttons allows a switch device user to change the highlighted year and see the percentages for each year.&lt;/p&gt;
&lt;h2 id=&quot;considerations&quot;&gt;Considerations&lt;/h2&gt;
&lt;p&gt;This implementation has some considerations from the accessibility point of view. If I had started with these controls, I would probably have built the whole graph a bit differently from a screen-reader and keyboard user perspective, but this time, I was building on top of the previous blog posts, so that was a constraint. I&amp;#39;ll share some concerns about this implementation and the reasoning behind my decisions. &lt;/p&gt;
&lt;h3 id=&quot;non-descriptive-buttons&quot;&gt;Non-Descriptive Buttons&lt;/h3&gt;
&lt;p&gt;First, the buttons in the UI have text content saying &amp;quot;Next year&amp;quot; and &amp;quot;Previous year,&amp;quot; and when a screen-reader user presses those buttons, nothing happens, especially if they&amp;#39;re non-sighted or have turned the screen off. This is because even though we move the currently selected year, we don&amp;#39;t move the accessibility focus. Or focus at all, for that matter. &lt;/p&gt;
&lt;p&gt;If I had added the button controls first, I would probably have made the &lt;code&gt;Labels&lt;/code&gt; component, which contains the selected year&amp;#39;s values, into a live region. That way, every time the content changes when the year changes, the content would have been announced. &lt;/p&gt;
&lt;p&gt;Also, I could have added that to this version, but I wanted to keep this blog post as simple as possible. Also, if a user navigates linearly, they would first encounter the graph and the data before hitting the buttons. That way, there would be context for buttons and a way to navigate through the years. If they are not navigating linearly, then the situation can be different, depending on the mode they&amp;#39;re using. &lt;/p&gt;
&lt;h3 id=&quot;additional-tab-stops&quot;&gt;Additional Tab Stops&lt;/h3&gt;
&lt;p&gt;Another consideration is that this implementation creates additional tab stops for a keyboard user. It&amp;#39;s only two, but if every time you press a key is painful, those two can be too much. &lt;/p&gt;
&lt;p&gt;In this case, a similar implementation I described for screen-reader users would also be better for keyboard users. That would reduce a lot of tab stops from the graph and replace them with just these two buttons. Of course, a button needs to be activated with a keypress, so there&amp;#39;s that.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked into how to add visible controls for navigating the graph. This approach has solved issues for those who might have trouble with path-based gestures or use a switch device. We&amp;#39;ve also looked into some considerations this implementation has related to screen-reader and keyboard navigation. &lt;/p&gt;
&lt;p&gt;This blog post series has provided two approaches to solving issues with pointer input-based graph navigation so far: Adding an overlay and focusable areas on top of the years in the graph and adding control buttons. As every app and use case is different, you might need one or both of them to make your graphs more accessible - or, in some cases, a different type of solution can be the answer. &lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-31/more-accessible-graphs-with-jetpack-compose-part-2-adding-keyboard-interaction/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 2: Adding Keyboard Interaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-08-16/more-accessible-graphs-with-jetpack-compose-part-3-differentiating-without/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 3: Differentiating without Color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/0b049a57c89a1fc73f0224c0f1e4130871f63ae9&quot;&gt;You can find the final code from this commit.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>How My App Won the 2nd Place in the WWCode App Deploy Hackathon</title>
    <link href="https://eevis.codes/blog/2023-12-14/how-my-app-won-the-2nd-place-in-the-wwcode-app-deploy-hackathon/" />
    <updated>2023-12-14T04:01:55.939Z</updated>
    <id>https://eevis.codes/blog/2023-12-14/how-my-app-won-the-2nd-place-in-the-wwcode-app-deploy-hackathon/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/YQxqtqj4Rj83syomLFlyc/e5fafc52b946be245ea6084917e0e65d/bragdoc-wwcode.png"/>]]>
      &lt;p&gt;A few weeks ago, I participated in my first mobile hackathon; it was so much fun! This hackathon was also the first more traditional (so, just over the weekend) for me and the second in general. The first was the Github Actions hackathon in 2020, organized by Dev.to, and it spanned throughout weeks.&lt;/p&gt;
&lt;p&gt;The hackathon I participated in was &lt;a href=&quot;https://hopin.com/events/wwcode-app-deploy/registration&quot;&gt;App Deploy Hackathon from Women Who Code&lt;/a&gt;, and the goal was to build an Android app and use any of the Google APIs available. WWCode organized the hackathon in partnership with Google Play.&lt;/p&gt;
&lt;p&gt;I can already share that I won second place in the hackathon! You can read more from &lt;a href=&quot;https://www.womenwhocode.com/blog/diversifying-the-tech-ecosystem-winners-and-highlights-from-the-wwcode-app-deploy-hackathon-with-google-play&quot;&gt;Women Who Code&amp;#39;s Winner announcement blog post&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ll share my journey from planning to submission in this blog post. You&amp;#39;ll also find links to the code and a video demonstrating the app. I haven&amp;#39;t yet published the app in Google Play because there was not enough time to jump through all the hoops, but I will probably release it sometime at the beginning of next year.&lt;/p&gt;
&lt;h2 id=&quot;planning-for-the-hackathon&quot;&gt;Planning for the Hackathon&lt;/h2&gt;
&lt;p&gt;I registered for the hackathon about two weeks before the event. Those two weeks were hectic, but I had some time to think about the hackathon app idea. I spent quite a bit of time with the Google API Explorer and tried to come up with some ideas.&lt;/p&gt;
&lt;p&gt;I came up with two more concrete ideas: a menstruation-tracking app with the features I&amp;#39;d love to have with Google Health Connect, or an app for documenting accomplishments and wins at work and generating a text for a performance review cycle from those items. That second one would use the PaLM API (or so I thought). &lt;/p&gt;
&lt;p&gt;When the start of the hackathon came closer, I decided to go with the second idea. I researched and had my building plan (or the general lines) ready for action. &lt;/p&gt;
&lt;h3 id=&quot;the-story-behind-the-app-idea&quot;&gt;The Story Behind the App Idea&lt;/h3&gt;
&lt;p&gt;The app idea was something that had been on my mind for a long, long time. Someone shared &lt;a href=&quot;https://jvns.ca/blog/brag-documents/&quot;&gt;Julia Evans&amp;#39; blog post &amp;quot;Get your work recognized: write a brag document&amp;quot;&lt;/a&gt; a long time ago, and I loved the idea. I have often started writing my brag documents, but I have always forgotten about them because they have been in forms I don&amp;#39;t use that much, like Google Docs or other similar solutions.  &lt;/p&gt;
&lt;p&gt;So, I wanted to build an app for that - maybe I&amp;#39;ll remember it better if I see the application icon on my home screen. That remains to be seen, but now I have an app I can use. &lt;/p&gt;
&lt;h2 id=&quot;building-the-app&quot;&gt;Building the App&lt;/h2&gt;
&lt;p&gt;Finally, the hackathon started. I live in Finland (UTC+2), and the event times were in Eastern Time (UTC-5), so for me, the building started only on Saturday morning. &lt;/p&gt;
&lt;p&gt;I&amp;#39;m an Android developer and had a good idea of what architecture components I&amp;#39;m using, so I got the basis of the app built and running relatively quickly. Then, it was time to start implementing the actual things that the app does. Everything went smoothly until I was about to integrate the PaLM API into my app. &lt;/p&gt;
&lt;h3 id=&quot;need-to-switch-the-api&quot;&gt;Need to Switch the API&lt;/h3&gt;
&lt;p&gt;As I mentioned, I had already planned to use the PaLM API and Vertex AI in the app. I started integrating it, had everything I needed for it to work, and... I got an error that it&amp;#39;s not yet available in Finland. I had forgotten to check its availability - like, it&amp;#39;s available in many countries, so Finland must surely be one of them. Well, it was not.&lt;/p&gt;
&lt;p&gt;My solution relied on some sort of text generation, and the hackathon rules also said that I&amp;#39;d need to use a Google API as part of the app. I didn&amp;#39;t have time to start over, so I had to come up with a solution.&lt;/p&gt;
&lt;p&gt;I went for a walk to clear my head and returned to my computer with a plan. I&amp;#39;d use Open AI&amp;#39;s Chat Completions API for text generation and Firebase Crashlytics as the Google API. &lt;/p&gt;
&lt;p&gt;After trial and error, and some prompting, I integrated the Chat Completions API into my app. I felt so great the first time I got the generated text on my screen - a moment I&amp;#39;ll definitely store in my memory and my BragDoc app. &lt;/p&gt;
&lt;h3 id=&quot;integrating-crashlytics&quot;&gt;Integrating Crashlytics&lt;/h3&gt;
&lt;p&gt;The other thing I needed to add was Crashlytics. I was surprised at how easy it was - I usually encounter some problems when following instructions, but everything went smoothly this time.&lt;/p&gt;
&lt;p&gt;It might have something to do with the fact that I&amp;#39;ve added Firebase Notifications to an app before, and back then, I had a lot of problems. Nothing seemed to work. That was until I realized that I had added the &lt;code&gt;google-services.json&lt;/code&gt; to the wrong folder as I had added it manually. This time, I used the helper in Android Studio, and let me tell you, that went smoothly.&lt;/p&gt;
&lt;h3 id=&quot;the-mvp-of-the-app&quot;&gt;The MVP of the App&lt;/h3&gt;
&lt;p&gt;The idea of the application is simple: There is a screen where you can add your wins and accomplishments throughout the year. You can see them listed on that screen and delete them if needed - meaning there is no edit option. &lt;/p&gt;
&lt;p&gt;There is also another screen for generating a summary of your accomplishments. It takes all the achievements not part of a summary yet and generates a short-ish summary from them with the help of OpenAI&amp;#39;s Chat Completions API. You can see all the generated summaries on the same screen.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s a video of the app: &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=UnFLjTYSkeE&quot;&gt;https://www.youtube.com/watch?v=UnFLjTYSkeE&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;submission&quot;&gt;Submission&lt;/h2&gt;
&lt;p&gt;The deadline for submission was on Monday (or, in my time, early Tuesday morning). As I had to work on Monday, I had to get the submission as ready as possible before I started working. That was a busy morning - I had to quickly create all the assets I&amp;#39;d need, the video for the submission, and decide if I wanted to add a possibility to download the app.&lt;/p&gt;
&lt;p&gt;At this phase, all the skills I have with Figma and the VN video editor came in handy - without some prior knowledge, I&amp;#39;m not sure if I would have been able to submit everything in time. I&amp;#39;m still slightly unsatisfied with the video, but it is what it is. &lt;/p&gt;
&lt;p&gt;I wanted to get the app to the Play Store, but if you&amp;#39;ve ever submitted anything there, you know it requires time. There are so many questions to answer and checks to complete. I had to rule that option out, and I started wondering what I could do.&lt;/p&gt;
&lt;p&gt;Luckily, I was logged in to the Firebase console and noticed a link, &amp;quot;App distribution.&amp;quot; I started reading about it and realized that this is exactly what I need. A few moments later, I had the app available through there and could add a link to it to my hackathon submission.&lt;/p&gt;
&lt;p&gt;I got it in in time and was so happy and proud. I had started developing something and had made the MVP out of it. It&amp;#39;s not perfect, and I could improve many things, but I finished it in time. &lt;/p&gt;
&lt;h2 id=&quot;winner-announcement&quot;&gt;Winner Announcement&lt;/h2&gt;
&lt;p&gt;On the morning of December 7th, I opened my email and found a message from Women Who Code congratulating me on being one of the hackathon&amp;#39;s winners. I was so happy and honored!&lt;/p&gt;
&lt;p&gt;I also want to congratulate the other winners - Marissa Campa with RecipeApp — A Recipe Finder, and Melike Altin, Arzu Caner, Ezgi Efe, and Yagmur Baran Karakus with Travel Brew!&lt;/p&gt;
&lt;h2 id=&quot;final-thoughts-and-link-to-code&quot;&gt;Final Thoughts and Link to Code&lt;/h2&gt;
&lt;p&gt;I&amp;#39;m proud of myself for completing this MVP of the hackathon app and being one of the winners. I also did well with the scoping of the app. It&amp;#39;s so easy to start building an app that&amp;#39;s too big for the time constraints of the hackathon and get lost in different features. A weekend is a short time (especially when you&amp;#39;re making something alone), so it needs to be scoped well if the goal is to get something done.&lt;/p&gt;
&lt;p&gt;If you want to see the application code, you can find it in my &lt;a href=&quot;https://github.com/eevajonnapanula/BragDoc&quot;&gt;BragDoc-repository in Github&lt;/a&gt;. I will improve and publish the app in the Play Store later, so stay tuned! I will share about it on my socials, so if you want to get your hands on it, follow me on &lt;a href=&quot;https://www.linkedin.com/in/eevajonna/&quot;&gt;LinkedIn (Eeva-Jonna Panula)&lt;/a&gt; or &lt;a href=&quot;https://mas.to/@eevis&quot;&gt;Mastodon (@eevis@mas.to)&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hopin.com/events/wwcode-app-deploy/registration&quot;&gt;App Deploy Hackathon from Women Who Code&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.womenwhocode.com/blog/diversifying-the-tech-ecosystem-winners-and-highlights-from-the-wwcode-app-deploy-hackathon-with-google-play&quot;&gt;Women Who Code&amp;#39;s Winner announcement blog post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jvns.ca/blog/brag-documents/&quot;&gt;Julia Evans&amp;#39; blog post &amp;quot;Get your work recognized: write a brag document&amp;quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/BragDoc&quot;&gt;BragDoc-repository in Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/in/eevajonna/&quot;&gt;LinkedIn (Eeva-Jonna Panula)&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mas.to/@eevis&quot;&gt;Mastodon (@eevis@mas.to)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Year in Review - 2023 Edition</title>
    <link href="https://eevis.codes/blog/2023-12-29/year-in-review-2023-edition/" />
    <updated>2023-12-29T04:37:41.528Z</updated>
    <id>https://eevis.codes/blog/2023-12-29/year-in-review-2023-edition/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/35tEZ1i0m9Cb1SRMB6Xdnp/2c9b8fe1b055b71d8276256ddc7b2644/yir-2023-square.png"/>]]>
      &lt;p&gt;Another year, another Year in Review blog post! When I started listing things from 2023, I realized a lot had happened. And I probably don&amp;#39;t even remember everything. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve written similar posts before, and you can find them from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/&quot;&gt;Year in Review - 2021 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-01-07/year-in-review-2022-edition/&quot;&gt;Year in Review - 2022 Edition&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;accomplishments&quot;&gt;Accomplishments&lt;/h2&gt;
&lt;p&gt;There are so many things I&amp;#39;ve accomplished this year. One of them is surviving through the year. It has been one of the most challenging years so far. But more about that later. &lt;/p&gt;
&lt;p&gt;My latest accomplishment was winning 2nd place in the WWCode App Deploy hackathon. I was so proud of the app, and even more proud when I got the letter that they selected my app as second best. If you want to know more about the app, I wrote a blog post: &lt;a href=&quot;https://eevis.codes/blog/2023-12-14/how-my-app-won-the-2nd-place-in-the-wwcode-app-deploy-hackathon/&quot;&gt;How My App Won the 2nd Place in the WWCode App Deploy Hackathon&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Another thing I want to mention was completing the Women Developer Academy in October. I&amp;#39;m so happy about meeting all the people and getting to know them. I also can&amp;#39;t wait for all the future conferences and other events where we&amp;#39;ll meet in person! I wrote a blog post about my experiences: &lt;a href=&quot;https://eevis.codes/blog/2023-11-24/women-developer-academy-europe-2023-my-experience/&quot;&gt;Women Developer Academy Europe 2023 - My Experience&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;During the fall, we organized a study group for learning Android with my colleague at Oura. It was for a Finnish organization called Mimmit Koodaa (Women Code would be the literal translation), and we got a great group of Android newbies joining us throughout the months. Even though fall turned out to be full of events and other things, I&amp;#39;m happy that we got to organize this study group. &lt;/p&gt;
&lt;p&gt;Another notable thing is that I was a guest on two podcasts: &lt;a href=&quot;https://www.techtalkswithmadona.com/p/season-3-episode-2-what-does-it-mean&quot;&gt;Tech Talks with Madona&lt;/a&gt; and &lt;a href=&quot;https://www.womenwhocode.com/blog/conversations-87-web-accessibility&quot;&gt;Women Who Code podcast&lt;/a&gt;. It was so fun to record both of them! I recommend checking both podcasts out (not just the episodes I&amp;#39;m in); there are so many gems in the episodes. &lt;/p&gt;
&lt;p&gt;Finally, I&amp;#39;m Dev Top Author for the second year in a row! Dev has always been a dear community to me, as it has been so welcoming and inclusive, so this is an honor. &lt;/p&gt;
&lt;h2 id=&quot;speaking&quot;&gt;Speaking&lt;/h2&gt;
&lt;p&gt;In 2023, I did more in-person talks than last year. Most of the engagements were conferences, with a couple of meetups.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Conferences:&lt;ul&gt;
&lt;li&gt;Android Worldwide&lt;/li&gt;
&lt;li&gt;Women Who Code Dev Summit - Mobile &amp;amp; Web&lt;/li&gt;
&lt;li&gt;Droidcon Italy&lt;/li&gt;
&lt;li&gt;KKON&lt;/li&gt;
&lt;li&gt;Droidcon London&lt;/li&gt;
&lt;li&gt;Saavuta 2023&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Meetups&lt;ul&gt;
&lt;li&gt;Aurajoki Overflow&lt;/li&gt;
&lt;li&gt;Accessibility Morning Mixer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also had to cancel two talks: RN EU and DevFest Stockholm. &lt;/p&gt;
&lt;p&gt;Even though I enjoyed every talk I gave, the most meaningful were Droidcons. Those were both in person, and I got to travel to Turin and London, both for the very first time. Meeting everyone there made me really feel like I&amp;#39;m part of the Android community. And as they were in-person conferences, that facilitated some very interesting discussions. &lt;/p&gt;
&lt;p&gt;And there was one more thing: I&amp;#39;ve struggled a lot at work, feeling like I&amp;#39;m not seen as the professional I am, so being a speaker and having all those conversations felt great. &lt;/p&gt;
&lt;p&gt;Even though I didn&amp;#39;t give that many talks last year, I&amp;#39;m happy about the ones I gave. So, it was a good year in terms of speaking.&lt;/p&gt;
&lt;h2 id=&quot;writing&quot;&gt;Writing&lt;/h2&gt;
&lt;p&gt;2023 was alright from a writing perspective. I published 16 blog posts and found new mediums, like Medium (pun intended). I mainly wrote about Android-related topics, with some more career or inclusion-related posts. &lt;/p&gt;
&lt;p&gt;I started publishing on Medium, and many of my posts were published on ProAndroidDev-publication. One of my posts also got boosted by the Medium team, which was a pleasant surprise. &lt;/p&gt;
&lt;p&gt;Many newsletters featured my blog posts, which was also nice. I made it to Dev&amp;#39;s week&amp;#39;s top 7 list twice, first with my #WeCoded-post &amp;quot;&lt;a href=&quot;https://dev.to/eevajonnapanula/the-language-we-use-matters-9mn&quot;&gt;The Language We Use Matters&lt;/a&gt;&amp;quot; and then with my recent post &amp;quot;How My App Won the 2nd Place in the WWCode App Deploy Hackathon&amp;quot;. &lt;/p&gt;
&lt;p&gt;Here are some personal favorites:&lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Improving Android Accessibility with Modifiers in Jetpack Compose
&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2023-07-11&quot;&gt;July 11th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;Read the post Improving Android Accessibility with Modifiers in Jetpack Compose
&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;I wrote about using Jetpack Compose&amp;#39;s modifiers to improve accessibility. In the blog post, I cover four modifiers: &lt;code&gt;clickable&lt;/code&gt;, &lt;code&gt;toggleable&lt;/code&gt;, &lt;code&gt;selectable&lt;/code&gt;, and &lt;code&gt;magnifier&lt;/code&gt;. This post is also something I&amp;#39;ve turned into a talk; for example, I gave a talk by the same name and contents at Droidcon Italy.&lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Sometimes I Feel Like I&#39;m Invisible - Experiences of a Woman in Tech&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2023-02-05&quot;&gt;February 5th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2023-02-05/sometimes-i-feel-like-im-invisible-experiences-of-a-woman-in-tech/&quot;&gt;Read the post Sometimes I Feel Like I&#39;m Invisible - Experiences of a Woman in Tech&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;Another personal favorite is this blog post about how I sometimes feel invisible as a woman in tech. In the post, I wrote about personal experiences and microaggressions in general. I wrote it after some events at work, but it wasn&amp;#39;t just about those events - it&amp;#39;s something I&amp;#39;ve been feeling my whole career. &lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;More Accessible Graphs with Jetpack Compose Part 4: On-Screen Control Buttons&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2023-12-09&quot;&gt;December 9th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2023-12-09/more-accessible-graphs-with-jetpack-compose-part-4-on-screen-control-buttons/&quot;&gt;Read the post More Accessible Graphs with Jetpack Compose Part 4: On-Screen Control Buttons&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;This blog post was fun to write. Or, rather, to build. In the series of blog posts about more accessible graphs, I&amp;#39;ve been improving a line graph, and this blog post adds on-screen control buttons for easier navigation. It was also the blog post that got boosted by the Medium team. &lt;/p&gt;
&lt;h2 id=&quot;other-things&quot;&gt;Other Things&lt;/h2&gt;
&lt;p&gt;Of course, this year has been about more than just writing, talking, and accomplishments. It&amp;#39;s not a secret that this year, once again, has been a fight with burnout for me. I wish I could tell you a story about how I was burnt out and re-discovered myself, and now I&amp;#39;m one of those success stories out there. &lt;/p&gt;
&lt;p&gt;No, the story is way more boring and closer to reality for many living with burnout. There have been better times, and there have been worse times. I wish taking sickness leave would solve this, but it hasn&amp;#39;t, so here we are, trying to solve the problems at work that cause this. And it doesn&amp;#39;t help that because of the brain injury I had, I&amp;#39;m more prone to burnout than some others.&lt;/p&gt;
&lt;p&gt;But this year hasn&amp;#39;t been all bad either! I cherish many beautiful moments - like when I went kayaking in spring to one of the Finnish national parks (Kolovesi), home to the Saimaa ringed seal. I saw many of them from a distance, but when I unpacked my kayak on the dock at the end of the trip, I heard a sound behind me. I turned around, and I saw that there had been something big. A large fish, I thought to myself and continued unpacking. &lt;/p&gt;
&lt;p&gt;Then I realized that someone was staring at me from the other side of the dock. It was Saimaa ringed seal! I slowly tried to get my phone to get a picture, and right before I was ready to take the photo, the seal just looked at me with those side-eyes (you know, the same that dogs do often) and slowly disappeared to the water. So, no, I didn&amp;#39;t get a photo. But the memory will live forever.&lt;/p&gt;
&lt;p&gt;Another notable thing from 2023 was that I started writing my master&amp;#39;s thesis. It&amp;#39;s about Android developers and how to help us to make more accessible apps. I plan to finish it by summer. I&amp;#39;m taking part-time study leave for that, but we&amp;#39;ll see what the spring brings. I&amp;#39;ve learned to not to plan too strictly. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve tried to develop new hobbies to improve my recovery outside work, and I started golf. First, going to the green card course was just to be able to join my sister when she&amp;#39;s playing, but I kind of fell into a rabbit hole, and now I&amp;#39;m taking lessons during winter to improve my game, and I can&amp;#39;t wait for the snow to melt to get to play.&lt;/p&gt;
&lt;h2 id=&quot;what-about-2024&quot;&gt;What About 2024?&lt;/h2&gt;
&lt;p&gt;I&amp;#39;m not making many promises for 2024 either (as I didn&amp;#39;t do last year), but I hope to finish my master&amp;#39;s thesis and graduate next year. I also hope to find balance and write a different type of year in review a year from now. &lt;/p&gt;
&lt;p&gt;There are also some longer-term dreams, like writing a book. I&amp;#39;ve been dreaming about it for a long time, and the time to make that dream come true is closer than it used to be. But first, I really need to finish that master&amp;#39;s thesis. &lt;/p&gt;
&lt;p&gt;Overall, I hope 2024 will bring peace, time with old and new friends, a lot of time spent outside, and lots of new learnings. &lt;/p&gt;
&lt;p&gt;I wish you a better year in 2024!&lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/&quot;&gt;Year in Review - 2021 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-01-07/year-in-review-2022-edition/&quot;&gt;Year in Review - 2022 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-12-14/how-my-app-won-the-2nd-place-in-the-wwcode-app-deploy-hackathon/&quot;&gt;How My App Won the 2nd Place in the WWCode App Deploy Hackathon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-11-24/women-developer-academy-europe-2023-my-experience/&quot;&gt;Women Developer Academy Europe 2023 - My Experience&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.techtalkswithmadona.com/p/season-3-episode-2-what-does-it-mean&quot;&gt;Tech Talks with Madona&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.womenwhocode.com/blog/conversations-87-web-accessibility&quot;&gt;Women Who Code podcast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/eevajonnapanula/the-language-we-use-matters-9mn&quot;&gt;The Language We Use Matters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;Read the post Improving Android Accessibility with Modifiers in Jetpack Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-02-05/sometimes-i-feel-like-im-invisible-experiences-of-a-woman-in-tech/&quot;&gt;Read the post Sometimes I Feel Like I&amp;#39;m Invisible - Experiences of a Woman in Tech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-12-09/more-accessible-graphs-with-jetpack-compose-part-4-on-screen-control-buttons/&quot;&gt;Read the post More Accessible Graphs with Jetpack Compose Part 4: On-Screen Control Buttons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Exploring Health Connect Pt. 1 - Setting Up Permissions</title>
    <link href="https://eevis.codes/blog/2024-01-12/exploring-health-connect-pt-1-setting-up-permissions/" />
    <updated>2024-01-12T08:49:31.417Z</updated>
    <id>https://eevis.codes/blog/2024-01-12/exploring-health-connect-pt-1-setting-up-permissions/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2mfp2KPJcCxHHi0WoYLQN9/4a6282d98ac92a1d822d4f0f96875f72/health-connect-permissions.png"/>]]>
      &lt;p&gt;I&amp;#39;ve been really intrigued to try out Google Health Connect and build an app that utilizes it. When I started thinking about what I&amp;#39;d make, I realized that the one thing that I would need is a custom period tracker. &lt;/p&gt;
&lt;p&gt;There are some apps to track your menstruation, but they&amp;#39;re usually pretty complex, and most require an account. And when all I want to do is track the times when I&amp;#39;m bleeding, I thought I could build my own app.&lt;/p&gt;
&lt;p&gt;Also, many of these apps are often so freaking heteronormative that I don&amp;#39;t want to use them. What if I don&amp;#39;t have just (CIS) men as potential partners? What if I don&amp;#39;t need to care about possible pregnancy? What if I only want to track when I&amp;#39;m bleeding? &lt;/p&gt;
&lt;p&gt;Also, why are these apps often colored pink? And why do they (almost) always assume it&amp;#39;s women using these apps? Some non-binary folks and trans-men menstruate, too, you know. And yes, some apps are more inclusive than others. However, it is still frustrating how many apps make these assumptions about people who menstruate. &lt;/p&gt;
&lt;p&gt;So yes. I decided to build my own app. I&amp;#39;m also writing a couple of blog posts that will share the steps I took to make the first version of my period-tracking app. Even though I&amp;#39;m writing about the menstruation-related Google Health records, these same steps apply to other records. &lt;/p&gt;
&lt;p&gt;In this first blog post, I&amp;#39;m sharing how to set things up and ask for permission from the user. The following blog post (published next week) will be about writing and reading data from Health Connect. After these two posts, the app I&amp;#39;m building will look like this:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/3bI9reg5JMR37elhZZfKL3/e45f9e3ac9c0e148d54e461d6caeef14/period_app_rec.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;Before diving into the coding, let&amp;#39;s discuss Health Connect a bit.&lt;/p&gt;
&lt;h2 id=&quot;what-is-health-connect&quot;&gt;What is Health Connect?&lt;/h2&gt;
&lt;p&gt;Health Connect is a way to share data between apps on Android. It provides a centralized way to handle health and fitness data, giving users control over which apps they want to share their data. You can learn more from &lt;a href=&quot;https://health.google/health-connect-android/&quot;&gt;Health Connect by Android site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As Health Connect is related to sensitive data, you must fill out a form to use it in production. I&amp;#39;m not pushing this app to production anytime soon, so I have yet to check that procedure out. You can read more from the &lt;a href=&quot;https://developer.android.com/health-and-fitness/guides/health-connect/publish/request-access&quot;&gt;&amp;quot;Request access to Health Connect data types&amp;quot;-documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;starting-point-for-the-app&quot;&gt;Starting Point for the App&lt;/h2&gt;
&lt;p&gt;So before I started implementing integration to Health Connect, I built the UI ready. The app is simple: It has one screen that lists all the periods and a dialog to pick the start and end dates. Here&amp;#39;s a picture of the UI:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2s96dAvyRhu0DnyiYAYlN1/88bea8cb594b6820a1cff5ed2f428e06/period-app-start.png&quot; alt=&quot;PeriodApp Ui, which has menstruation periods listed. The data is grouped into years, and under each year each period is displayed visually as thick red line over a wider, white rectangle. The start and end dates are marked above the start and end of the red line. The topmost period does not have end date, and the color fades to white.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;The code from where we&amp;#39;re starting to build this is in &lt;a href=&quot;https://github.com/eevajonnapanula/PeriodApp/tree/start&quot;&gt;the start-branch&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;To be able to use Health Connect, we need to do a couple of things: Add necessary dependencies, add required permissions to &lt;code&gt;AndroidManifest&lt;/code&gt;, and ask permission to use Health Connect data from the user. Let&amp;#39;s start with the first two. &lt;/p&gt;
&lt;h3 id=&quot;add-dependencies-and-permissions-to-androidmanifest&quot;&gt;Add Dependencies and Permissions to AndroidManifest&lt;/h3&gt;
&lt;p&gt;A separate Health Connect client is available, and we need to add it to module-level &lt;code&gt;build.gradle.kts&lt;/code&gt;. By the time of writing this blog post, the latest version is &lt;code&gt;1.1.0-alpha06&lt;/code&gt;, but be sure to &lt;a href=&quot;https://developer.android.com/jetpack/androidx/releases/health-connect&quot;&gt;check the latest release from Jetpack library versions.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, let&amp;#39;s add this line to module-level &lt;code&gt;build.gradle.kts&lt;/code&gt;: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// build.gradle.kts

implementation(&amp;quot;androidx.health.connect:connect-client:1.1.0-alpha06&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need to add the permissions to &lt;code&gt;AndroidManifest&lt;/code&gt;. The following two lines are enough as this application only reads and writes menstruation-related data. They go just before the &lt;code&gt;&amp;lt;application&amp;gt;&lt;/code&gt;-element. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;// AndroidManifest.xml
&amp;lt;uses-permission 
    android:name=&amp;quot;android.permission.health.READ_MENSTRUATION&amp;quot; /&amp;gt;
&amp;lt;uses-permission 
    android:name=&amp;quot;android.permission.health.WRITE_MENSTRUATION&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can read more about the permissions for each record Health Connect provides from &lt;a href=&quot;https://developer.android.com/health-and-fitness/guides/health-connect/plan/data-types#permissions&quot;&gt;the documentation&amp;#39;s &amp;quot;Permissions&amp;quot;-section&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;ask-for-health-connect-permissions&quot;&gt;Ask for Health Connect Permissions&lt;/h3&gt;
&lt;p&gt;The next thing to do is ask the user for permission. In the context of this app, we want to read the data when the user opens the app, so we need to ask for permission when the user opens the app for the first time (or any time if the permissions are not granted). &lt;/p&gt;
&lt;p&gt;We could implement a second check for writing permissions when the user wants to save data to Health Connect. However, I&amp;#39;m trying to make things straightforward, so in this app, we&amp;#39;ll ask for both writing and reading permissions simultaneously, and the user needs to give them both to be able to use the app. &lt;/p&gt;
&lt;p&gt;So, let&amp;#39;s start by adding an &lt;code&gt;intent-filter&lt;/code&gt; to &lt;code&gt;MainActivity&lt;/code&gt; in &lt;code&gt;AndroidManifest&lt;/code&gt; so that we can actually open the permissions dialog:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;// AndroidManifest.xml

&amp;lt;intent-filter&amp;gt;
    &amp;lt;action 
        android:name=&amp;quot;android.intent.action.VIEW_PERMISSION_USAGE&amp;quot;/&amp;gt;
    &amp;lt;category 
        android:name=&amp;quot;android.intent.category.HEALTH_PERMISSIONS&amp;quot;/&amp;gt;
&amp;lt;/intent-filter&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without these two, the permission dialog won&amp;#39;t open. In a production app, you&amp;#39;d probably add a privacy policy screen, and these intent filters would be part of that activity. Read more in &lt;a href=&quot;https://developer.android.com/health-and-fitness/guides/health-connect/develop/get-started#show-privacy-policy&quot;&gt;Show privacy policy section in Health Connect documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next, let&amp;#39;s create a new file and name it &lt;code&gt;HealthConnectManager.&lt;/code&gt; It will serve as a connection point to Health Connect. Inside the file, we declare a class and pass it context as a parameter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// HealthConnectManager.kt

class HealthConnectManager(private val context: Context) {
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To be able to ask for the permissions, we need three things inside the class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// HealthConnectManager.kt

private val healthConnectClient by lazy { 
    HealthConnectClient.getOrCreate(context) 
}

suspend fun hasAllPermissions(): Boolean = 
    healthConnectClient
        .permissionController
        .getGrantedPermissions()
        .containsAll(PERMISSIONS)

fun requestPermissionsActivityContract(): ActivityResultContract&amp;lt;Set&amp;lt;String&amp;gt;, Set&amp;lt;String&amp;gt;&amp;gt; {
    return PermissionController
        .createRequestPermissionResultContract()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We store the &lt;code&gt;healthConnectClient&lt;/code&gt; we use later as a private variable and lazily initialize it. We&amp;#39;re also implementing a check for permissions we need and getting the activity result contract.&lt;/p&gt;
&lt;p&gt;In addition, we define the permissions we need as a separate variable. In the case of this app, and at this point, we need just two:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// HealthConnectManager.kt

val PERMISSIONS =
    setOf(
        HealthPermission
            .getReadPermission(MenstruationPeriodRecord::class),
        HealthPermission
            .getWritePermission(MenstruationPeriodRecord::class),
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The view model is another layer we&amp;#39;ll need to define between the API and UI. The view model needs an instance of &lt;code&gt;HealthConnectManager&lt;/code&gt;, which we pass in as a parameter. &lt;/p&gt;
&lt;p&gt;We also define a variable to store whether the app has all permissions, a variable to store the permission launcher, and a function to check the permissions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// PeriodViewModel.kt

var permissionsGranted by mutableStateOf(false)

val permissionsLauncher = 
    healthConnectManager
        .requestPermissionsActivityContract()

private fun checkPermissions() {
    viewModelScope.launch {
        permissionsGranted = 
            healthConnectManager
                .hasAllPermissions()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we have all we need to implement the permission check. In &lt;code&gt;MainScreen&lt;/code&gt;-composable, we&amp;#39;ll define the actual launcher with &lt;code&gt;rememberLauncherForActivityResult&lt;/code&gt;, and pass in the &lt;code&gt;permissionsLauncher&lt;/code&gt; we defined in the view model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

val permissionsLauncher =
    rememberLauncherForActivityResult(
        viewModel.permissionsLauncher
    ) {
    // TODO
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We will fetch the records in the next blog post in the TODO-block.&lt;/p&gt;
&lt;p&gt;After that, we use &lt;code&gt;LaunchedEffect&lt;/code&gt; to check if the user has given permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

LaunchedEffect(Unit) {
    if (viewModel.permissionsGranted) {
        // TODO
    } else {
        permissionsLauncher.launch(PERMISSIONS)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here again, the TODO-block is where we&amp;#39;ll call the function to get records. &lt;/p&gt;
&lt;p&gt;With these changes, the app will prompt the user to give the necessary Health Connect permissions, as seen in the video at the beginning of this blog post.  &lt;/p&gt;
&lt;h2 id=&quot;what-if-user-doesnt-give-permission&quot;&gt;What If User Doesn&amp;#39;t Give Permission?&lt;/h2&gt;
&lt;p&gt;There is just one more thing in the context of this blog post. What if the user doesn&amp;#39;t give Health Connect permissions to the app? Or what if they accidentally press cancel when the permission screen is prompted?&lt;/p&gt;
&lt;p&gt;We&amp;#39;ll need to add a possibility to re-ask the permissions. In the &lt;code&gt;LazyColumn&lt;/code&gt; in &lt;code&gt;MainScreen&lt;/code&gt;, let&amp;#39;s add an &lt;code&gt;item&lt;/code&gt; with a &lt;code&gt;Button&lt;/code&gt; conditionally as the first thing in the column if the user has not granted permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt 

if (viewModel.permissionsGranted.not()) {
    item {
        Button(onClick = {...}) {
            Text(&amp;quot;Give permissions to Health Connect&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This button would be visible only when the permissions are not granted. &lt;/p&gt;
&lt;p&gt;When the user presses the button, the settings for Health Connect should open. The following code does that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

Button(
    onClick = {
        val intent = if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            // HCM is an import alias for HealthConnectManager from the Health Connect client
            Intent(HCM.ACTION_MANAGE_HEALTH_PERMISSIONS)
                .putExtra(
                    Intent.EXTRA_PACKAGE_NAME, 
                    context.packageName
                )
        } else {
            Intent(
                HealthConnectClient.ACTION_HEALTH_CONNECT_SETTINGS
            )
        }
        startActivity(context, intent, null)
    }
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;onClick&lt;/code&gt;-handler, we first check if the user has Android 14 (SDK-version 34), as Health Connect permission management is slightly different starting from that version. If the answer is yes, we set the intent to be &lt;code&gt;HealthConnectManager.ACTION_MANAGE_HEALTH_PERMISSIONS&lt;/code&gt; with the extra of the package name to open the dedicated Health Connect permission management screen. &lt;/p&gt;
&lt;p&gt;If the user has an earlier version, then the intent is &lt;code&gt;HealthConnectClient.ACTION_HEALTH_CONNECT_SETTINGS&lt;/code&gt;, the general Health Connect settings page.&lt;/p&gt;
&lt;p&gt;You can find all the changes done in this blog post in this commit: &lt;a href=&quot;https://github.com/eevajonnapanula/PeriodApp/commit/b85b08fea2c694e44312776972b6cc830e84af31&quot;&gt;Add Health Connect Permissions&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve been looking at how to set up things for Health Connect and how to ask for the permissions from the user. As I&amp;#39;m building this app for my personal use, there might be some corners I&amp;#39;ve cut, so be sure to check the official documentation if you&amp;#39;re building an app that uses Health Connect in production. &lt;/p&gt;
&lt;p&gt;The next blog post will be about reading and writing data, as well as updating and deleting it. So stay tuned; I will publish it next week!&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://health.google/health-connect-android/&quot;&gt;Health Connect by Android site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/health-and-fitness/guides/health-connect/publish/request-access&quot;&gt;&amp;quot;Request access to Health Connect data types&amp;quot;-documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/PeriodApp/tree/start&quot;&gt;the start-branch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/jetpack/androidx/releases/health-connect&quot;&gt;check the latest release from Jetpack library versions.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/health-and-fitness/guides/health-connect/develop/get-started#show-privacy-policy&quot;&gt;Show privacy policy section in Health Connect documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/health-and-fitness/guides/health-connect/plan/data-types#permissions&quot;&gt;the documentation&amp;#39;s &amp;quot;Permissions&amp;quot;-section&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/PeriodApp/commit/b85b08fea2c694e44312776972b6cc830e84af31&quot;&gt;Add Health Connect Permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Exploring Health Connect Pt. 2 - Reading and Writing Data</title>
    <link href="https://eevis.codes/blog/2024-01-19/exploring-health-connect-pt-2-reading-and-writing-data/" />
    <updated>2024-01-19T04:22:38.527Z</updated>
    <id>https://eevis.codes/blog/2024-01-19/exploring-health-connect-pt-2-reading-and-writing-data/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/53PKKwam2oye19EbNf1Ycr/fead72ee24ce872708b45b2646e6ce01/health-connect-permissions__1_.png"/>]]>
      &lt;p&gt;I&amp;#39;ve been building a period tracking app for myself with the help of Health Connect. Last week, I published the first blog post where I explained how to set up Health Connect and asked the user to give permission to use their data. &lt;/p&gt;
&lt;p&gt;I initially thought I&amp;#39;d write one blog post about building this app - now, it&amp;#39;s turning into (at least) three. But I have to say that I&amp;#39;ve enjoyed this process a lot. &lt;/p&gt;
&lt;p&gt;This blog post will look into reading and writing data with Health Connect. There will be one more blog post about updating and deleting data before we get to the point where the app looks like in this video:  &lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/3bI9reg5JMR37elhZZfKL3/e45f9e3ac9c0e148d54e461d6caeef14/period_app_rec.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;h2 id=&quot;reading-and-writing-data&quot;&gt;Reading and Writing Data&lt;/h2&gt;
&lt;p&gt;Before going into code, I want to mention one handy app: &lt;a href=&quot;https://developer.android.com/health-and-fitness/guides/health-connect/test/health-connect-toolbox&quot;&gt;Health Connect Toolbox&lt;/a&gt;. With its help, you can write and read data from Health Connect, which helps debug issues like if the data was saved at all. Trust me, I know. &lt;/p&gt;
&lt;p&gt;Health Connect Toolbox also helps with the first task we&amp;#39;re looking into: Reading records. If you don&amp;#39;t have any other app writing that particular type of data to Health Connect, you can use the Health Connect Toolbox to write data and then see that data in your own app.&lt;/p&gt;
&lt;p&gt;Now, we have the permissions to read and write data from/to Health Connect, so we can add the implementation for those actions.&lt;/p&gt;
&lt;h3 id=&quot;read-records&quot;&gt;Read Records&lt;/h3&gt;
&lt;p&gt;The first action we&amp;#39;re implementing is reading data. Let&amp;#39;s start from the closest point to Health Connect: &lt;code&gt;HealthConnectManager&lt;/code&gt;, which we defined in the previous blog post. We need a function to read data from Health Connect:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// HealthConnectManager.kt 

suspend fun readMenstruationRecords(): 
    List&amp;lt;MenstruationPeriodRecord&amp;gt; {
        val request = ReadRecordsRequest(
            recordType = MenstruationPeriodRecord::class,
            timeRangeFilter = 
                TimeRangeFilter.after(
                    LocalDateTime.now().minusMonths(12)
                ),
        )
        val response = healthConnectClient.readRecords(request)
        return response.records
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, we define the request, which is &lt;code&gt;ReadRecordsRequest&lt;/code&gt;. The type is &lt;code&gt;MenstruationPeriodRecord::class&lt;/code&gt;, as we want to read menstruation period data from Health Connect. We also add a time range filter going back 12 months from today. This end date will probably be changed while developing this application, but for now, let&amp;#39;s stick with data from the past 12 months. &lt;/p&gt;
&lt;p&gt;Next, we call &lt;code&gt;readRecords&lt;/code&gt; from the Health Connect Client. It returns a response with records as one of its values, and we want to return those from this function. &lt;/p&gt;
&lt;p&gt;In the view model, we need to make a few more adjustments. Basically, we want to add a wrapper that checks if the app has all permissions to try to read (or write) data. We also need to add the actual data reading function, as well as store the read data to a variable in the view model. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s start with the wrapper and add the function: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// PeriodViewModel.kt

private suspend fun tryWithPermissionsCheck(
    block: suspend () -&amp;gt; Unit
) {
    permissionsGranted = 
        healthConnectManager.hasAllPermissions()
    try {
        if (permissionsGranted) {
            block()
        }
    } catch (remoteException: RemoteException) {
        Log.e(&amp;quot;Error getting records:&amp;quot;, &amp;quot;${remoteException.message}&amp;quot;)
    } catch (securityException: SecurityException) {
        Log.e(&amp;quot;Error getting records:&amp;quot;, &amp;quot;${securityException.message}&amp;quot;)
    } catch (ioException: IOException) {
        Log.e(&amp;quot;Error getting records:&amp;quot;, &amp;quot;${ioException.message}&amp;quot;)
    } catch (illegalStateException: IllegalStateException) {
        Log.e(&amp;quot;Error getting records:&amp;quot;, &amp;quot;${illegalStateException.message}&amp;quot;)
    } catch (e: Exception) {
        Log.e(&amp;quot;Error getting records:&amp;quot;, &amp;quot;${e.message}&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&amp;#39;s a suspend function that takes in a block that will be executed if the app has permissions. First, we check the permission situation from the Health Connect Manager and store the value to the &lt;code&gt;permissionsGranted&lt;/code&gt; variable we defined in the previous blog post. &lt;/p&gt;
&lt;p&gt;Then, if the permissions are granted, we try to execute the block we&amp;#39;re passing in. There is also a somewhat granular exception handling - for this app, for now, it&amp;#39;s just logging the errors, but this would be the place to handle errors in other ways, too. &lt;/p&gt;
&lt;p&gt;Now that we have permission checks in place, we can read the Health Connect records and store them in the view model. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// PeriodViewModel.kt 

var periods by mutableStateOf&amp;lt;List&amp;lt;MenstruationPeriodRecord&amp;gt;&amp;gt;(emptyList())

fun getInitialRecords() {
    viewModelScope.launch {
        tryWithPermissionsCheck {
            getPeriodRecords()
        }
    }
}

private fun getPeriodRecords() {
    viewModelScope.launch {
        periods = healthConnectManager.readMenstruationRecords()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this block of code, we first define the &lt;code&gt;periods&lt;/code&gt;-variable as a mutable state and list of &lt;code&gt;MenstruationPeriodRecord&lt;/code&gt;. Then we define the &lt;code&gt;getInitialRecords&lt;/code&gt;-function, which uses &lt;code&gt;tryWithPermissionsCheck&lt;/code&gt; on calling &lt;code&gt;getPeriodRecords&lt;/code&gt;, which calls &lt;code&gt;readMenstruationRecords&lt;/code&gt; from the Health Connect Manager. Finally, it sets the records returned by this function to the &lt;code&gt;periods&lt;/code&gt;-variable. &lt;/p&gt;
&lt;p&gt;The final piece in the puzzle is the UI layer. The first bigger change is to change the type of data we&amp;#39;re passing around: from the &lt;code&gt;Period&lt;/code&gt;-data class we defined for the initial UI to &lt;code&gt;MenstruationPeriodRecord&lt;/code&gt;. You can see all the changes required from the final commit, linked at the end of the blog post.&lt;/p&gt;
&lt;p&gt;In the previous post, we left a couple of blocks with TODO-comments. Let&amp;#39;s replace them with the actual data reading functions: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

val permissionsLauncher =
    rememberLauncherForActivityResult(
        viewModel.permissionsLauncher
    ) {
        viewModel.getInitialRecords()
    }

LaunchedEffect(Unit) {
    if (viewModel.permissionsGranted) {
        viewModel.getInitialRecords()
    } else {
        permissionsLauncher.launch(PERMISSIONS)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We call the &lt;code&gt;getInitialRecords&lt;/code&gt; from the view model in these two places. What happens here is that when the user opens this activity, the &lt;code&gt;LaunchedEffect&lt;/code&gt; is run. The app fetches the data from Health Connect if permissions are granted. If they&amp;#39;re not, it calls the &lt;code&gt;launch&lt;/code&gt;-method from the &lt;code&gt;permissionsLauncher&lt;/code&gt; we defined to ask for permissions from the user. When the permissions are granted, it calls the &lt;code&gt;getInitialRecords&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;We also refactor the code to use &lt;code&gt;periods&lt;/code&gt; from the view model instead of the hard-coded &lt;code&gt;periods&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;With these changes, we actually get data from the Health Connect! If you don&amp;#39;t have any other apps writing this type of data, adding some records with the Health Connect Toolbox is a way to test that everything works as intended. &lt;/p&gt;
&lt;p&gt;This is enough if you want to just read data from other apps. But if you want to add and modify data, then keep reading. &lt;/p&gt;
&lt;h3 id=&quot;write-records&quot;&gt;Write Records&lt;/h3&gt;
&lt;p&gt;For writing data to Health Connect, we&amp;#39;ll take similar steps as when reading data: First, the Health Connect Manager, then the view model, and finally, the UI layer. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s start with the Health Connect Manager and add a function to write records:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// HealthConnectManager.kt

suspend fun writeMenstruationRecords(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    val records = listOf(menstruationPeriodRecord)
    try {
        healthConnectClient.insertRecords(records)
        Toast.makeText(context, &amp;quot;Successfully insert records&amp;quot;, Toast.LENGTH_SHORT).show()
    } catch (e: Exception) {
        Toast.makeText(context, e.message.toString(), Toast.LENGTH_SHORT).show()
        Log.e(&amp;quot;Error&amp;quot;, &amp;quot;Message: ${e.message}&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;writeMenstruationRecords&lt;/code&gt;-function takes in the record to write. As Health Connect Client&amp;#39;s &lt;code&gt;insertRecords&lt;/code&gt; takes in a list of records, we wrap our &lt;code&gt;menstruationPeriodRecord&lt;/code&gt; in a list. &lt;/p&gt;
&lt;p&gt;Within a &lt;code&gt;try - catch&lt;/code&gt; block, we call the &lt;code&gt;insertRecords&lt;/code&gt;-method, and if everything is successful, we&amp;#39;ll show a toast message to the user. If there is an error, we show a toast message with the error and then log the error.&lt;/p&gt;
&lt;p&gt;In the view model, we use the &lt;code&gt;tryWithPermissionsCheck&lt;/code&gt;-function to check the permissions before writing data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// PeriodViewModel.kt

fun writeMenstruationRecord(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    viewModelScope.launch {
        tryWithPermissionsCheck {
            healthConnectManager.writeMenstruationRecords(
                menstruationPeriodRecord
             )
            getPeriodRecords()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function takes in the &lt;code&gt;MenstruationPeriodRecord&lt;/code&gt; and writes it to Health Connect with the Health Connect Manager&amp;#39;s &lt;code&gt;writeMenstruationRecords&lt;/code&gt;. After that, it re-fetches the records from Health Connect to update the UI. I haven&amp;#39;t found a way to observe the data yet, so it needs to be updated manually. &lt;/p&gt;
&lt;p&gt;The next thing is to modify the UI. The date range picker dialog already takes in a function that is called when the user presses &amp;quot;Save&amp;quot; in the dialog. Let&amp;#39;s edit it to allow writing data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// DateRangePickerDialog.kt

TextButton(onClick = {
    val startTime =
        if (dateRangePickerState.selectedStartDateMillis != null) {
            Instant.ofEpochMilli(
                dateRangePickerState.selectedStartDateMillis!!
            )
        } else
            Instant.now()

    val endTime =
        if (dateRangePickerState.selectedEndDateMillis != null) {
            Instant.ofEpochMilli(
                dateRangePickerState.selectedEndDateMillis!!
            )
        } else
            startTime.plusSeconds(60) // This needs to be different from start time, but within the same day to work as intended on my code.
    ...
}) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;#39;s start unpacking the code. First, we need the start date and end date, and as Health Connect records take in &lt;code&gt;Instant&lt;/code&gt;, we need to convert the milliseconds &lt;code&gt;dateRangePickerState&lt;/code&gt; uses to &lt;code&gt;Instant&lt;/code&gt;. We also need to check if the values are null - and if they are, then we set the start date to &lt;code&gt;Instant.now()&lt;/code&gt; and the end date to be 60 seconds from the start date. This 60-second difference is because, for Health Connect menstruation period records, the end date can&amp;#39;t be null, but it can&amp;#39;t be equal to the start time either. &lt;/p&gt;
&lt;p&gt;The app allows the user to not set the end date because if the menstruation is ongoing, there&amp;#39;s no way to know when it ends. So, I&amp;#39;ve modified the code to check if the end time is the same date as the start time, and if so, then it&amp;#39;s treated as null elsewhere in the code - like in the visualization. &lt;/p&gt;
&lt;p&gt;Now, we have enough data to actually save the new period. The &lt;code&gt;DateRangePickerDialog&lt;/code&gt; takes in a &lt;code&gt;selectedPeriod&lt;/code&gt;, which is either an empty record with default values, or a record we&amp;#39;re updating. We define this &lt;code&gt;updated&lt;/code&gt; record with the start and end times inside the &lt;code&gt;onClick&lt;/code&gt;-handler and call the &lt;code&gt;onConfirm&lt;/code&gt;-function we pass to &lt;code&gt;DateRangePickerDialog&lt;/code&gt;: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// DateRangePickerDialog.kt

TextButton(onClick = {
... 

val updated = MenstruationPeriodRecord(
        startTime = startTime,
        endTime = endTime,
        startZoneOffset = selectedPeriod.startZoneOffset,
        endZoneOffset = selectedPeriod.endZoneOffset,
        metadata = selectedPeriod.metadata,
    )
    onConfirm(updated)
}) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in &lt;code&gt;MainScreen&lt;/code&gt;, we add a new function, &lt;code&gt;saveMenstruationPeriod&lt;/code&gt; that calls the view model&amp;#39;s function by the same name. We also modify the &lt;code&gt;onConfirm&lt;/code&gt;-function a bit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

fun saveMenstruationPeriod(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    viewModel.writeMenstruationRecord(
        menstruationPeriodRecord
    )
}

....

DateRangePickerDialog( ... ) { updatedSelectedPeriod -&amp;gt;
    showDatePickerDialog = false
    selectedPeriod = updatedSelectedPeriod
    saveMenstruationPeriod(updatedSelectedPeriod)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, now we&amp;#39;ve allowed the user to add new records. But what if the user inserts incorrect data and wants to edit the dates? They need to be able to edit the records, which we&amp;#39;ll look at in the next blog post.  &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked at how to read and write data with Health Connect. You can find all the changes made to the code in the context of this blog post in this commit: &lt;a href=&quot;https://github.com/eevajonnapanula/PeriodApp/commit/8e345160e1d88bf9cd6f1c082c13e72083b1d44f&quot;&gt;Read and write data from/to Health Connect&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In next week&amp;#39;s blog post, we&amp;#39;ll look into how to update and delete Health Connect data. So stay tuned for that!&lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/health-and-fitness/guides/health-connect/test/health-connect-toolbox&quot;&gt;Health Connect Toolbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-01-12/exploring-health-connect-pt-1-setting-up-permissions/&quot;&gt;Exploring Health Connect Pt. 1 - Setting Up Permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/PeriodApp/commit/8e345160e1d88bf9cd6f1c082c13e72083b1d44f&quot;&gt;Read and write data from/to Health Connect&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Exploring Health Connect pt. 3 - Updating and Deleting Data</title>
    <link href="https://eevis.codes/blog/2024-01-28/exploring-health-connect-pt-3-updating-and-deleting-data/" />
    <updated>2024-01-28T09:07:54.490Z</updated>
    <id>https://eevis.codes/blog/2024-01-28/exploring-health-connect-pt-3-updating-and-deleting-data/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/5wr3mkgcK5hLcXErUB4Ff/cb85384db27ef5095a663452e05dad9c/health-connect-permissions-3.png"/>]]>
      &lt;p&gt;I&amp;#39;ve been building a period tracking app for myself, and my other goal has been to explore Health Connect. There are already two blog posts in this series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-01-12/exploring-health-connect-pt-1-setting-up-permissions/&quot;&gt;Exploring Health Connect Pt. 1 - Setting Up Permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-01-19/exploring-health-connect-pt-2-reading-and-writing-data/&quot;&gt;Exploring Health Connect Pt. 2 - Reading and Writing Data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the third (and last) blog post of the MVP version of this period tracker. We&amp;#39;ll look into updating and deleting Health Connect data. After changes in this blog post, the app will look like this:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/3bI9reg5JMR37elhZZfKL3/e45f9e3ac9c0e148d54e461d6caeef14/period_app_rec.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;So, let&amp;#39;s start with allowing the user to update data. &lt;/p&gt;
&lt;h2 id=&quot;update-records&quot;&gt;Update Records&lt;/h2&gt;
&lt;p&gt;Updating records is pretty straightforward after the changes we made in the context of the last blog post. First, in Health Connect Manager, we add &lt;code&gt;updateMenstruationRecords&lt;/code&gt;-function: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// HealthConnectManager.kt

suspend fun updateMenstruationRecords(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    try {
        healthConnectClient.updateRecords(
            listOf(menstruationPeriodRecord),
        )

        Toast.makeText(context, &amp;quot;Successfully updated records&amp;quot;, Toast.LENGTH_SHORT).show()
    } catch (e: Exception) {
        Toast.makeText(context, e.message.toString(), Toast.LENGTH_SHORT).show()
        Log.e(&amp;quot;Error&amp;quot;, &amp;quot;Message: ${e.message}&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works the same way as writing - takes in a record, calls Health Connect Client&amp;#39;s function &lt;code&gt;updateRecords&lt;/code&gt; with a list of records, and then shows a toast message if an update is successful. &lt;/p&gt;
&lt;p&gt;Then, in the view model, we add a function to call &lt;code&gt;updateMenstruationRecords&lt;/code&gt; in Health Connect Manager:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// PeriodViewModel.kt

fun updateMenstruationRecord(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    viewModelScope.launch {
        tryWithPermissionsCheck {
            healthConnectManager
              .updateMenstruationRecords(
                  menstruationPeriodRecord
              )
          getPeriodRecords()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a successful update, we re-read the records from Health Connect to update our UI as we did with the write-action.&lt;/p&gt;
&lt;p&gt;Then, in the UI layer, we don&amp;#39;t need to modify the &lt;code&gt;DateRangePickerDialog&lt;/code&gt;, as the functionality already returns an updated record when the user presses the &amp;quot;Save&amp;quot;-button. However, we need to use different methods in &lt;code&gt;MainScreen&lt;/code&gt; for writing and updating data. So, let&amp;#39;s define an enum to represent whether we&amp;#39;re updating or editing the dates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

enum class DatePickerAction {
    UPDATE, INSERT
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want to remember that value as a mutable state: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

var datePickerAction by remember {
    mutableStateOf(DatePickerAction.INSERT)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we set the value for this &lt;code&gt;datePickerAction&lt;/code&gt; when we&amp;#39;re opening the date range picker dialog: When it&amp;#39;s from the floating action button, we set it to &lt;code&gt;INSERT,&lt;/code&gt; and when it&amp;#39;s from the edit button in a period row, we set it to &lt;code&gt;UPDATE.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then, in the &lt;code&gt;onConfirm&lt;/code&gt;-block of the &lt;code&gt;DateRangePickerDialog&lt;/code&gt;, we check the action and call the correct method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

when (datePickerAction) {
    DatePickerAction.INSERT -&amp;gt; 
        saveMenstruationPeriod(updatedSelectedPeriod)
    DatePickerAction.UPDATE -&amp;gt; 
        updateMenstruationPeriod(updatedSelectedPeriod)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you&amp;#39;re wondering, the update method we&amp;#39;re calling looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt
fun updateMenstruationPeriod(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    viewModel.updateMenstruationRecord(
        menstruationPeriodRecord
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright, after these changes, the user can update the dates. Yay! But we have one more action to add: Deleting a record. &lt;/p&gt;
&lt;h2 id=&quot;delete-records&quot;&gt;Delete Records&lt;/h2&gt;
&lt;p&gt;Once again, let&amp;#39;s start with the Health Connect Manager and add a function there. For deleting records, it&amp;#39;s a little bit more complex than for other actions. First, we want to get (possible) client record ID from the metadata of the menstruation period record we&amp;#39;re passing in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// HealthConnectManager.kt 

suspend fun deleteMenstruationRecords(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    val clientRecordIdList = if (menstruationPeriodRecord.metadata.clientRecordId != null) {
        listOf(
            menstruationPeriodRecord.metadata.clientRecordId
        )
    } else {
        emptyList()
    }
   ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is because the delete operation from Health Connect Client requires both client record IDs and IDs. Next, we pass in the variable we just defined and the id of the &lt;code&gt;MenstruationPeriodRecord&lt;/code&gt; we&amp;#39;re deleting. Otherwise, this function uses the same patterns as the others:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// HealthConnectManager.kt

suspend fun deleteMenstruationRecords(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    ...
    try {
        healthConnectClient.deleteRecords(
            MenstruationPeriodRecord::class,
            recordIdsList = listOf(
                menstruationPeriodRecord.metadata.id
            ),
            clientRecordIdsList = clientRecordIdList,
        )

        Toast.makeText(context, &amp;quot;Successfully deleted records&amp;quot;, Toast.LENGTH_SHORT).show()
    } catch (e: Exception) {
        Toast.makeText(context, e.message.toString(), Toast.LENGTH_SHORT).show()
        Log.e(&amp;quot;Error&amp;quot;, &amp;quot;Message: ${e.message}&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next up is the view model. It&amp;#39;s as straightforward as with the other operations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// PeriodViewModel.kt

fun deleteMenstruationRecord(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    viewModelScope.launch {
        tryWithPermissionsCheck {
            healthConnectManager.deleteMenstruationRecords(
                menstruationPeriodRecord
            )
            getPeriodRecords()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, once again, we call the &lt;code&gt;deleteMenstruationRecords&lt;/code&gt; from Health Connect Manager after checking if the app has all the permissions. After that, we re-read the records after deleting the item.&lt;/p&gt;
&lt;p&gt;We&amp;#39;ll need to do a couple of things on the UI side. First, we want to change the edit button in the &lt;code&gt;PeriodRow&lt;/code&gt; to a menu with two actions: Edit and Delete, as seen in the picture: &lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2nksagTzmwl702GWO5g66i/1a518674ebdd63b4b6146aaacc780e71/Frame_3.png&quot; alt=&quot;On the top, there&#39;s a picture of the old implementation with the period card element having a edit-button with pen-icon at the end of the row. Under it, there are three arrows pointing down to a period card element, that has three dots instead of the pen icon, and opened menu that has Edit- and Delete-actions.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;As this blog post is about Health Connect (and getting rather long), I&amp;#39;m leaving out the details of the UI change, but if you&amp;#39;re interested, the change is reflected in the final commit. &lt;/p&gt;
&lt;p&gt;To complete the deletion, we need to pass the action to execute when the user presses &amp;quot;Delete&amp;quot;. This, again, is pretty similar to what we did with other options: Defining a method and then calling it in the correct place in the UI:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

fun deleteMenstruationPeriod(
    menstruationPeriodRecord: MenstruationPeriodRecord
) {
    viewModel.deleteMenstruationRecord(
        menstruationPeriodRecord
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

PeriodRow(
    ...
    onDeleteIconClick = {
         deleteMenstruationPeriod(it)
        ...
    }
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these changes, the user can delete the period. From a UX perspective, it would be good to add a dialog to ask if the user really wants to delete that menstruation record, but for the sake of this blog post being straightforward, I decided to leave implementing that to some later moment.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked at how to update and delete data from Health Connect. You can find all the changes made to the code in the context of this blog post in this commit: &lt;a href=&quot;https://github.com/eevajonnapanula/PeriodApp/commit/db3f267fc31cf259c101aa30f003eb51cdf34d53&quot;&gt;Update and delete data from Health Connect&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m planning to develop this app further, including things like adding menstruation flow to the mix (that is, how much you bleed when menstruating?). From a development perspective, this will be interesting because it&amp;#39;s another type of record, and working with aggregated data adds more complexity to the code. I won&amp;#39;t promise to publish that blog post next week, but sometime in the near-ish future!&lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-01-12/exploring-health-connect-pt-1-setting-up-permissions/&quot;&gt;Exploring Health Connect Pt. 1 - Setting Up Permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-01-19/exploring-health-connect-pt-2-reading-and-writing-data/&quot;&gt;Exploring Health Connect Pt. 2 - Reading and Writing Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/PeriodApp/commit/db3f267fc31cf259c101aa30f003eb51cdf34d53&quot;&gt;Update and delete data from Health Connect&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Font Size Considerations for Accessibility</title>
    <link href="https://eevis.codes/blog/2024-02-02/font-size-considerations-for-accessibility/" />
    <updated>2024-02-02T03:46:04.480Z</updated>
    <id>https://eevis.codes/blog/2024-02-02/font-size-considerations-for-accessibility/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/59uABnSGvwV4NZVQtWQkd0/90e2111584d61a6c6649a030db552af0/font-size__2_.png"/>]]>
      &lt;p&gt;Discussions about accessibility, especially in software development, often center around screen reader accessibility. With that, I mean that if accessibility is considered, it&amp;#39;s mostly about, for example, content descriptions, setting correct semantics, and then leaving it there. &lt;/p&gt;
&lt;p&gt;Don&amp;#39;t get me wrong, it is important. But the problems arise when only taking these actions for accessibility and calling it done. In some apps, those actions also bring many good things for other users. But in others, many issues that create barriers to usage can be missed if the attention is only on screen reader-related accessibility.&lt;/p&gt;
&lt;p&gt;In this and a couple of future blog posts, I&amp;#39;ll share some things you can check to find accessibility issues, and suggest ideas for fixing them. As you might guess, these are not related to screen reader accessibility. This first blog post is about font sizing.&lt;/p&gt;
&lt;p&gt;By the way, I&amp;#39;m currently writing my master&amp;#39;s thesis, and its goal is to create an accessibility checklist for Android devs to help create more accessible Android apps. The initial checklist is live, and I&amp;#39;m collecting feedback, so if you want to help me, check the &lt;a href=&quot;https://android-a11y-checks.netlify.app/checks&quot;&gt;Android Accessibility Checklist&lt;/a&gt; and then answer a couple of questions in this &lt;a href=&quot;https://forms.gle/FeY5VUBEX2P728C38&quot;&gt;Google Form&lt;/a&gt;. I&amp;#39;m really, really thankful for anyone participating!&lt;/p&gt;
&lt;p&gt;Alright, let&amp;#39;s get started. &lt;/p&gt;
&lt;h2 id=&quot;font-size&quot;&gt;Font Size&lt;/h2&gt;
&lt;p&gt;One interesting thing related to font sizing is that about 25% of Android users increase the font size. (source: &lt;a href=&quot;https://appt.org/en/stats&quot;&gt;Appt.org&lt;/a&gt;) So this is a big group of our users! Sometimes, accessibility is dismissed because it&amp;#39;s said to be something that concerns only a small group of users. A quarter of the user base is already a considerable amount of users - actually, it can be bigger than, for example, some language your app supports. &lt;/p&gt;
&lt;p&gt;But if the user increases font size, it might cause problems with, for example, text overflowing or being clipped. We will explore some of these issues later in this blog post, but first, let&amp;#39;s talk about how to test your app for different font sizes.&lt;/p&gt;
&lt;h2 id=&quot;testing-font-size&quot;&gt;Testing Font Size&lt;/h2&gt;
&lt;p&gt;For testing the font size, you have at least two options: Using a phone (real device or emulator) or Compose previews. Let&amp;#39;s look at each of them in the following subsections. &lt;/p&gt;
&lt;h3 id=&quot;on-phone&quot;&gt;On Phone&lt;/h3&gt;
&lt;p&gt;You can test your app by changing the font size in your Accessibility settings. It&amp;#39;s most likely located in &lt;em&gt;Settings &amp;gt; Accessibility &amp;gt; Display size and text &amp;gt; Font Size&lt;/em&gt;. Turn the font size to the biggest option.&lt;/p&gt;
&lt;p&gt;After increasing the font size, go through your app. Notice anything that does not work? Some text is unreadable? You&amp;#39;ve found the problems. The next section will contain some suggestions on how to fix these issues.&lt;/p&gt;
&lt;h3 id=&quot;with-compose-previews&quot;&gt;With Compose Previews&lt;/h3&gt;
&lt;p&gt;You can also test how different font sizes behave with Compose&amp;#39;s previews. If you&amp;#39;re working pre-Compose 1.6, you&amp;#39;ll need to define font scales explicitly. You can do that via the &lt;code&gt;fontScale&lt;/code&gt;-attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Preview(fontScale = 1.8f)
@Composable
fun ComponentPreview() {
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compose 1.6 brings something handy: Multipreview API templates, specifically &lt;code&gt;@PreviewFontScale&lt;/code&gt;.  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@PreviewFontScale
@Preview
@Composable
fun ComponentPreview() {
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the picture below, you can see how it adds previews for all the configured font sizes (85%, 100%, 115%, 130%, 150%, 180%, and 200%) for my graph example app:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/5gFCvidX64iNrIV8jrAdjD/663fe2fa3d2f848f1ac119589998b69f/Screenshot_2024-02-01_at_6.27.25.png&quot; alt=&quot;Preview of 8 screens with a line graph with different font scales.&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;fixing-font-size&quot;&gt;Fixing Font Size&lt;/h2&gt;
&lt;p&gt;Okay, you tested your app, and you&amp;#39;ve found a problem. What to do to fix it? I&amp;#39;m going to use one of my favorite answers: It depends. I&amp;#39;ll list a couple of issues and how to fix them. &lt;/p&gt;
&lt;h3 id=&quot;problem-font-itself-is-not-scalable&quot;&gt;Problem: Font Itself is not Scalable&lt;/h3&gt;
&lt;p&gt;If the font itself doesn&amp;#39;t scale, one reason could be that you haven&amp;#39;t configured the font size correctly. &lt;/p&gt;
&lt;p&gt;This &amp;quot;not configured correctly&amp;quot; mainly means that the font sizes are something other than &lt;code&gt;sp&lt;/code&gt;, scalable pixels. Check your text styling, and if it&amp;#39;s something other than &lt;code&gt;sp&lt;/code&gt; (for example, &lt;code&gt;dp&lt;/code&gt;), change it to &lt;code&gt;sp.&lt;/code&gt; That should fix the problem of the font itself not scaling.&lt;/p&gt;
&lt;h3 id=&quot;problem-text-cuts-out&quot;&gt;Problem: Text Cuts Out&lt;/h3&gt;
&lt;p&gt;Another possible problem is that you&amp;#39;ve defined the components wrapping the text as not flexible enough to accommodate bigger or longer texts. This usually leads to text either overflowing or being clipped - both making the user interface harder to use. &lt;/p&gt;
&lt;p&gt;One place where this often happens is graphs. It&amp;#39;s not always straightforward to accommodate all font sizes when you use something like Canvas that is not so responsive to begin with. And if you can&amp;#39;t, you should think about alternative ways to provide that data (you probably should anyway, because not everyone enjoys, or even understands, graphs).&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s one example from a graph example app, zoomed to 200%: &lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1k1KrK0A1bJQFsAwGVvjg9/0e915e6a1b011143441b59bee71f1a6d/Screenshot_2024-02-01_at_6.28.07.png&quot; alt=&quot;A screen with line graph. X and y-axis labels of the graph cut off, not showing them fully.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;The picture shows how x- and y-axis labels cut off because there is not enough space for them when the font size increases. One way to fix this is to add some room for growth for the labels. You can even make the paddings (or whichever thing you&amp;#39;ll need to wrangle here) dynamic based on the font scale. &lt;/p&gt;
&lt;p&gt;You can access the user&amp;#39;s font scale value in Compose through their configuration. To be more precise, with &lt;code&gt;LocalConfiguration.current.fontScale&lt;/code&gt;. It is a float value - for 100% font scale, it&amp;#39;s 1, and for 200% 2. &lt;/p&gt;
&lt;p&gt;The exact code for fixing the issues varies case by case, but here&amp;#39;s what I did on my graph (I will write a more extensive blog post about this later): &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// First, get the font scale
val fontScale = LocalConfiguration.current.fontScale

// Second, use it to scale the padding:
Canvas(
    modifier = Modifier
        .padding(
            bottom = GraphComponent.innerPadding * fontScale,
            start = GraphComponent.innerPadding * fontScale,
            end = GraphComponent.innerPadding / 2,
        )
... ) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, I get some breathing room for the axis labels. Here&amp;#39;s the same 200% font scale picture after the changes:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/10ZhEbUcMzC3ihuKB5FZMP/c71c3fbdfc59aad1382082f1c1259dbb/Screenshot_2024-02-01_at_6.46.18.png&quot; alt=&quot;A screen with a line graph. The y- and x-axis labels have enough space to be visible, and they&#39;re not cut off. The buttons under the graph don&#39;t fit well, and the Next year button doesn&#39;t show the arrow icon.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;I&amp;#39;d be happy to know if you&amp;#39;ve found other ways to solve these issues, especially with graphs and Canvas!&lt;/p&gt;
&lt;h3 id=&quot;problem-layout-is-not-responsive&quot;&gt;Problem: Layout is not Responsive&lt;/h3&gt;
&lt;p&gt;You might have noticed another problem related to font size in the previous pictures: When font size increases, buttons under the graph don&amp;#39;t accommodate well. This is a common problem with layouts that must fit two or more items in a row. &lt;/p&gt;
&lt;p&gt;One way to fix this is to allow flexibility on items. So, if these items fit on one row, they stay like that, but if not, they should be on two (or more) rows. Compose 1.5.0 added a nice, although experimental, layout composable called &lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#FlowRow(androidx.compose.ui.Modifier,androidx.compose.foundation.layout.Arrangement.Horizontal,androidx.compose.foundation.layout.Arrangement.Vertical,kotlin.Int,kotlin.Function1)&quot;&gt;&lt;code&gt;FlowRow&lt;/code&gt;&lt;/a&gt;, which does what I described. &lt;/p&gt;
&lt;p&gt;When I change the current implementation to use &lt;code&gt;FlowRow&lt;/code&gt; (So, just &lt;code&gt;Row&lt;/code&gt; -&amp;gt; &lt;code&gt;FlowRow&lt;/code&gt;), the buttons align nicely with 200% font size:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/6hEAh5HqYGzzOvQYKYL27W/d94396ccf8acf3d6f5eee09aab06615d/Screenshot_2024-02-01_at_6.57.04.png&quot; alt=&quot;A screen with a line graph. Buttons under the graph are on two lines, and the text for both of the buttons is fully visible.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked into font size and what problems might appear if different font sizes are not tested and accommodated. I&amp;#39;ve provided some examples of these problems and how to fix them. &lt;/p&gt;
&lt;p&gt;Have you encountered any of these problems? Or have you gotten user feedback about font sizes? Do you have alternative ways to fix the issues? I&amp;#39;d love to hear, so please share them!&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://appt.org/en/stats&quot;&gt;Appt.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/checks&quot;&gt;Android Accessibility Checklist&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://forms.gle/FeY5VUBEX2P728C38&quot;&gt;Google Form&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#FlowRow(androidx.compose.ui.Modifier,androidx.compose.foundation.layout.Arrangement.Horizontal,androidx.compose.foundation.layout.Arrangement.Vertical,kotlin.Int,kotlin.Function1)&quot;&gt;&lt;code&gt;FlowRow&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Testing with Accessibility Scanner</title>
    <link href="https://eevis.codes/blog/2024-02-16/testing-with-accessibility-scanner/" />
    <updated>2024-02-16T03:38:58.642Z</updated>
    <id>https://eevis.codes/blog/2024-02-16/testing-with-accessibility-scanner/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/7eSZtcL2l2O0hGa0ibdtdH/3b0c80f162b27ac7e721f02861a48b49/a11y-scanner.png"/>]]>
      &lt;p&gt;When I talk about accessibility with Android developers, I often hear that some easy-to-use tests should automatically catch every possible accessibility issue. And I get it. That would make the whole topic of accessibility so much easier and straightforward.&lt;/p&gt;
&lt;p&gt;But the truth is that automated testing only catches some things. Studies suggest that the maximum percentage of caught accessibility issues is 40-50%. Deque, the company that develops the aXe accessibility tool, claims that the number is 57% in their &lt;a href=&quot;https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/&quot;&gt;study of automated accessibility tools&lt;/a&gt;. These studies have been performed with websites, but based on my experience, I have no reason to believe that the number would be higher on Android. So manual testing is always needed.&lt;/p&gt;
&lt;p&gt;But for that 40-57% (or whatever the exact number is on Android accessibility testing), there are some tools to use. One of them is Accessibility Scanner, and in this blog post, I&amp;#39;ll discuss how to test your app with it. Let&amp;#39;s start with what it is.&lt;/p&gt;
&lt;h2 id=&quot;what-is-accessibility-scanner&quot;&gt;What Is Accessibility Scanner&lt;/h2&gt;
&lt;p&gt;Accessibility Scanner is a helpful tool for semi-automated accessibility testing. It&amp;#39;s an app which can catch issues from the following categories: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Content labels&lt;/li&gt;
&lt;li&gt;Touch target size&lt;/li&gt;
&lt;li&gt;Clickable items&lt;/li&gt;
&lt;li&gt;Text and image contrast&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It also suggests how to fix some of its findings and provides more info about the issues. However, it does not find every possible accessibility issue and thus does not guarantee the accessibility of your app. Manual testing is still needed. I keep saying this because I&amp;#39;ve seen these claims so many times that &amp;quot;Well, our test automation didn&amp;#39;t find any accessibility issues, so our app is accessible&amp;quot;. &lt;/p&gt;
&lt;p&gt;One great thing is that you can use Accessibility Scanner with any Android app - it can be a native Android app or built with cross-platform technologies such as Flutter or React Native. Or it can even be a PWA - Progressive Web App. &lt;/p&gt;
&lt;h2 id=&quot;using-accessibility-scanner&quot;&gt;Using Accessibility Scanner&lt;/h2&gt;
&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;/h3&gt;
&lt;p&gt;To use the Accessibility Scanner, you must first download it from Google Play: &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&quot;&gt;Accessibility Scanner&lt;/a&gt;. Once downloaded, you can turn it on from Accessibility settings (Settings -&amp;gt; Accessibility -&amp;gt; Accessibility Scanner -&amp;gt; Use service). This action adds a button overlay on your screen, and it&amp;#39;s the access point to using the Accessibility Scanner. &lt;/p&gt;
&lt;h3 id=&quot;usage&quot;&gt;Usage&lt;/h3&gt;
&lt;p&gt;Accessibility Scanner provides two options: Scan one screen or record multiple screens. Navigate to your app, then press the Accessibility Scanner button on the screen. It opens a menu where you can choose to record, take a snapshot (one screen), or turn the Accessibility scanner off.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/6mYFqb2UgfJ3cm2AzMzkBs/a795a502f7e39f49064ae254f48c9704/Accessibility_Scanner.png&quot; alt=&quot;Two screens in a row; the first has a light blue rectangle button with a checkmark overlaying the content of the screen, and the other has the menu opened with the following options: Record, Snapshot, Turn off and Collapse.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Press the record option to record a flow or multiple screens. Then, navigate through the screens. If your phone has a vibration turned on, you should feel a slight vibration every time the app takes a screenshot. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve noticed that sometimes I need to go back and forth to get a screenshot from some screens. I don&amp;#39;t know why this is, but navigating to another screen and back has usually worked for me - if not the first time, then the second time. &lt;/p&gt;
&lt;p&gt;You can end the recording by pressing the same button that has now transitioned into a stop button. If it&amp;#39;s not visible on the screen, then you can, for example, open the Quick settings (slide them down from the top of the screen). The button should re-appear. &lt;/p&gt;
&lt;h3 id=&quot;results&quot;&gt;Results&lt;/h3&gt;
&lt;p&gt;Okay, now you&amp;#39;ve either taken a snapshot or recorded a flow. The next step is to see the results and interpret them. &lt;/p&gt;
&lt;p&gt;The Accessibility Scanner provides an overview of the screens: &lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/5Fu08he32S4JbeLb5sxkBY/6b88bf8c8d8be60a92a93d8bf6a8a186/Screenshot_20240214_064950_1.png&quot; alt=&quot;Accessibility Scanner&#39;s screen where all the screenshot it took from the recording are displayed at the top on a row, and one selected screen is displayed under that row. The selected screen takes the most of the available screen space.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;You can navigate through the screens and see possible problems highlighted. If you prefer seeing the suggestions in a list view, you can find it from the top right corner, under the list icon:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/5Y6liIaOlKKn98xBT6Azi6/e6b344a3370446e0d327a9e89edebb36/List-icon.png&quot; alt=&quot;Accessibility Scanner&#39;s screen, where the toolbar&#39;s right side&#39;s list icon has been highlighted with a pink rectangle and an arrow pointing at it.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;Found issues are grouped either by screen or by category. When clicking the element on the screen, it displays a screenshot of the problematic element:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1ABqVNwLetcMysOzKs8IWk/797534d297f8b00a4fbf925e1fe8ab29/Screenshot_20240214_065050.png&quot; alt=&quot;An issue with a disabled Save button having poor color contrast zoomed in on the button. Under the screenshot, there are details about the issue.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;It usually also provides ideas on how to fix the issue. You can read more about different issues and their possible causes and fixes from the materials mentioned in the following section.&lt;/p&gt;
&lt;h3 id=&quot;learn-more-about-accessibility-scanner&quot;&gt;Learn More About Accessibility Scanner&lt;/h3&gt;
&lt;p&gt;Google has created materials to learn more about the Accessibility Scanner. There are two good text-based resources: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/accessibility/android/answer/6376570?hl=en&amp;amp;sjid=15077242903809662356-EU&quot;&gt;Get Started with Accessibility Scanner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/accessibility/android/answer/6376559?hl=en&amp;amp;sjid=15077242903809662356-EU&quot;&gt;Accessibility Scanner Results&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you prefer watching a video, they also have one on YouTube: &lt;a href=&quot;https://www.youtube.com/watch?v=i1gMzQv0hWU&quot;&gt;Accessibility Scanner - Accessibility on Android&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve discussed Accessibility Scanner, a tool for testing some parts of Android apps&amp;#39; accessibility. While it does not guarantee fully accessible apps, it is a great tool for finding low-hanging issues. &lt;/p&gt;
&lt;p&gt;Have you tried Accessibility Scanner? Or is it in regular use for you? If you&amp;#39;ve tried it, what was the most interesting thing you found? &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/&quot;&gt;Study of automated accessibility tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&quot;&gt;Accessibility Scanner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/accessibility/android/answer/6376570?hl=en&amp;amp;sjid=15077242903809662356-EU&quot;&gt;Get Started with Accessibility Scanner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/accessibility/android/answer/6376559?hl=en&amp;amp;sjid=15077242903809662356-EU&quot;&gt;Accessibility Scanner Results&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=i1gMzQv0hWU&quot;&gt;Accessibility Scanner - Accessibility on Android&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Another Year Of Being in Gender Minority in Tech</title>
    <link href="https://eevis.codes/blog/2024-03-01/another-year-of-being-in-gender-minority-in-tech/" />
    <updated>2024-03-01T07:31:34.759Z</updated>
    <id>https://eevis.codes/blog/2024-03-01/another-year-of-being-in-gender-minority-in-tech/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2xzv50hEY7GRNpJryKcwMq/d5503269e590aab177e6e2394a73b3d8/wecoded-2024__1_.png"/>]]>
      &lt;p&gt;It&amp;#39;s time for Dev&amp;#39;s annual We Coded campaign again, and it&amp;#39;s the fifth time I&amp;#39;m participating. You can find my previous contributions from the links above. This year, I want to share experiences from another year as someone in gender minority in tech. I&amp;#39;ll share some not-so-pleasant experiences and, after that, some moments of allyship. &lt;/p&gt;
&lt;p&gt;Note that the stories I&amp;#39;m telling are from encounters with (assumed) men -  and I&amp;#39;m referring to them generally as men here. I chose this approach because this celebration is about gender minorities in tech, and men belong to the majority. So, I&amp;#39;m not trying to paint a picture of men being something awful; it&amp;#39;s more about the minority-majority aspect here. &lt;/p&gt;
&lt;p&gt;Also, a content warning: I&amp;#39;m sharing some comments containing misogyny.&lt;/p&gt;
&lt;h2 id=&quot;sometimes-it-just-sucks&quot;&gt;Sometimes it Just Sucks&lt;/h2&gt;
&lt;p&gt;I&amp;#39;ve seen many men choose the path of not being a nice person. I&amp;#39;ll share some stories from the past year alone.&lt;/p&gt;
&lt;p&gt;There was this conversation in one Finnish tech-related Slack community where someone shared a piece of news about women not feeling welcome in tech. Multiple men were telling how this all is 90% in the heads of women, and how the way to solve the issues would be to stop using words like &amp;quot;tech bro&amp;quot; when describing, well, tech bros, and that it&amp;#39;s not a problem because they haven&amp;#39;t witnessed it.&lt;/p&gt;
&lt;p&gt;When people who had witnessed and experienced the problems spoke up, they were either just ignored or told that they were &amp;quot;white knights,&amp;quot; depending on the assumed gender.  &lt;/p&gt;
&lt;p&gt;Oh, and that conversation had my all-time favorite argument: &amp;quot;What about the x, y, and z fields? They don&amp;#39;t have a proper gender balance.&amp;quot; And that was said in a tech-centered Slack community, where the talk was about how women in tech feel excluded. I don&amp;#39;t know if you noticed, but the topic was about tech, not the other fields. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve also been told that women should respect men, and I don&amp;#39;t belong even in a kitchen, but a cave, to learn about respect all the things men have done for me. This person made the threat in March last year, after I shared a LinkedIn post about gender equality and how some technologies are biased. Here&amp;#39;s the whole comment:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7nsmmkph1xb9pmjkgs9a.png&quot; alt=&quot;LinkedIn comment where Marko R. says some really mean things.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There have been countless times that I&amp;#39;ve been speaking up about something related to tech, and my (man) colleague has needed to repeat what I&amp;#39;ve said to get the point through. And countless times when a man has been praised for something that I&amp;#39;ve done. &lt;/p&gt;
&lt;p&gt;When talking about tech, I&amp;#39;ve been physically blocked from the conversation, as in I was standing in the circle of people and having a conversation, but when we started talking about the tech, one person turned their back on me, forcing me out of the circle. &lt;/p&gt;
&lt;p&gt;And then there was this one incident, which paints a picture of our doubts: Is it me and something I did, or my gender? I was speaking at a conference and had just ended my talk. A person came to me and started giving their opinion about my talk. They used phrases like &amp;quot;I&amp;#39;m a speaker myself, and I teach speakers&amp;quot; and &amp;quot;You should have done the talk completely differently&amp;quot;. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s pause here, I&amp;#39;ll explain something. If you have ever spoken at an event, you know that the adrenaline rush speaking gives probably makes the minutes right after stepping down from the stage the worst time to give that kind of feedback. You will hardly remember anything the person said, just the feeling you had. &lt;/p&gt;
&lt;p&gt;So, I thanked the person for their feedback (that was all I could do) and went somewhere quiet to calm down. I later spoke with several co-speakers at the conference, and they confirmed what I had thought: The feedback that person gave was not okay.&lt;/p&gt;
&lt;p&gt;In those moments, it&amp;#39;s tough to be a person from gender minority in tech. You never know if they would&amp;#39;ve given that same feedback to anyone in that situation - even if the speaker was a man - or if it was because the person felt somehow threatened by there being someone professional on the stage who is not from the majority.&lt;/p&gt;
&lt;p&gt;The final example I wanted to share is from Dev. During the past year, I&amp;#39;ve shared several blog posts about gender equality, and last year&amp;#39;s #WeCoded-post hit the jackpot. I mean, I knew that there would be at least some hateful comments, but the content surprised me. It&amp;#39;s been getting worse year after year.&lt;/p&gt;
&lt;p&gt;The comments were moderated pretty fast, but they contained things like drawing a line between asking for equality to mass murders and about wishing for &amp;quot;simpler times&amp;quot; - so, the times when it was women&amp;#39;s job to just take care of the children and home, and be of service to men. And definitely not be a professional in any field. Oh, and then there was this one comment about enforcing equality being fascist. &lt;/p&gt;
&lt;p&gt;These are just some examples from the past year. I&amp;#39;ve faced a lot more, but this already paints a picture that tech is not yet equal for all genders. We need to do better.  &lt;/p&gt;
&lt;h2 id=&quot;you-can-be-an-ally&quot;&gt;You Can Be an Ally&lt;/h2&gt;
&lt;p&gt;Not all I&amp;#39;ve seen has been bad, and I&amp;#39;m thankful for that. I want to share some examples of allyship regarding gender equality in tech, too. &lt;/p&gt;
&lt;p&gt;First, remember that part where I told you I needed a man colleague to repeat what I had said for the thing to be heard? That same colleague did that tirelessly and kept acknowledging the problem (that they needed to repeat what I said) and speaking up about it. So, they weren&amp;#39;t trying to take credit for my ideas, but they amplified them, gave credit to me, and tried to solve the problem.&lt;/p&gt;
&lt;p&gt;Then there were the other speakers I mentioned in the example about the feedback at the conference. When I got myself back together and could talk about the experience without starting to cry (yes, it felt that bad), the other speakers were understanding and on my side. They acknowledged the problem and assured me I didn&amp;#39;t imagine the problem there. So, if you recognize yourself and remember being there and one of those people, thank you!&lt;/p&gt;
&lt;p&gt; There&amp;#39;s also one person close to me, without whom I wouldn&amp;#39;t have survived through the last years. We&amp;#39;ve had so many conversations about gender equality in tech and all the things I&amp;#39;ve seen and endured, and that person has always listened to me, acknowledged the inequalities, and never questioned my feelings. They&amp;#39;ve also let me just rant and haven&amp;#39;t tried to solve the problem in that situation. Everyone should have a person like that. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post,  I&amp;#39;ve shared my experiences from the past year as someone in gender minority in tech. There have been a lot of nasty things, but good things as well. Without the goods, I would have already left tech - something I&amp;#39;ve been thinking more and more about over the last year.  &lt;/p&gt;
&lt;p&gt;If you&amp;#39;re in gender minority in tech, how has the last year been for you? And if you aren&amp;#39;t, what are you doing for more equal tech? Share your thoughts in the comments!&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Why Your App Should Have Both Dark And Light Themes</title>
    <link href="https://eevis.codes/blog/2024-03-07/why-your-app-should-have-both-dark-and-light-themes/" />
    <updated>2024-03-07T04:11:50.757Z</updated>
    <id>https://eevis.codes/blog/2024-03-07/why-your-app-should-have-both-dark-and-light-themes/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/51VAEArPG6vZ6RFqXt9uPQ/fbc001ecbedf9ab8ea6da4bc8cd0a157/light-and-dark__1_.png"/>]]>
      &lt;p&gt;You might have heard that your app should have a dark theme available. Some say it&amp;#39;s because of preference, but it&amp;#39;s actually also an accessibility thing.&lt;/p&gt;
&lt;p&gt;This blog post itself doesn&amp;#39;t have the code to enable dark and light themes for the user. Instead, it explains why having both of them is essential and provides further reading on the topic — yes, with links to some code and instructions as well.  &lt;/p&gt;
&lt;h2 id=&quot;accessibility-of-dark-and-light-themes&quot;&gt;Accessibility of Dark and Light Themes&lt;/h2&gt;
&lt;p&gt;Many people use everything they can in a dark theme. After all, staring at screens for a long time might be easier if the user interface uses darker colors. Using a dark theme instead of a light one also saves some battery. &lt;/p&gt;
&lt;p&gt;However, many people choose to use the dark theme because it is more accessible for them. For example, some vision issues might cause discomfort because of light or even wash out the rest of the person&amp;#39;s vision. A white (or similar) background behind the text can cause these issues. Another reason is that dark themes often have better contrast (if they&amp;#39;re designed properly). &lt;/p&gt;
&lt;p&gt;Now, if the dark theme is so great, why not just have the dark theme for the app and forget about the light theme? While dark theme is more accessible for many, some people benefit from light theme. For example, people with astigmatism may find light theme more accessible, and people with dyslexia might prefer light mode for readability. For reference, see, for example, this article: &lt;a href=&quot;https://uxdesign.cc/dark-matters-342ff2c7cc&quot;&gt;Dark mode: How accessible design raises the bar - Layth Sihan&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Luckily, defining light and dark themes in modern Android development is straightforward. Let&amp;#39;s discuss the technical considerations of light and dark themes next. &lt;/p&gt;
&lt;h2 id=&quot;technical-considerations&quot;&gt;Technical Considerations&lt;/h2&gt;
&lt;p&gt;Starting from Android 10 (SDK 29), there is a system setting for switching between dark and light modes. If you define your themes correctly, your app will respect that system-wide setting by default. &lt;/p&gt;
&lt;p&gt;To define a dark theme for your app using themes from XML files, follow the instructions in Android documentation: &lt;a href=&quot;https://developer.android.com/develop/ui/views/theming/darktheme&quot;&gt;Implement dark theme&lt;/a&gt;. For apps using Jetpack Compose, the instructions are in &lt;a href=&quot;https://developer.android.com/jetpack/compose/designsystems/material3#color-scheme&quot;&gt;Material Design 3 in Compose - Color scheme&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;A handy tool for generating themes is &lt;a href=&quot;https://m3.material.io/theme-builder&quot;&gt;Material Theme Builder&lt;/a&gt;, a web tool for defining themes. You can use the web UI to define the colors and then export the theme and the color definitions to import to your project. It provides exports for both Compose and XML, as well as for Flutter, web, and Material Tokens. &lt;/p&gt;
&lt;p&gt;If you want to give the user more granular control and select the theme for your app different from the system setting, here&amp;#39;s an article with a tutorial on how to do that with Jetpack Compose: &lt;a href=&quot;https://medium.com/@sibel.nalop/dark-theme-switch-in-jetpack-compose-with-compositionlocal-f03bd54683bb&quot;&gt;Dark Theme Switch in Jetpack Compose with CompositionLocal - Sibel Nalop&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I mentioned that dark themes might have better contrast if they&amp;#39;re designed properly. So, when defining your theme colors for both light and dark themes, check for enough contrast between the background, texts, and other UI elements. The Material Design docs explain color contrast: &lt;a href=&quot;https://m3.material.io/foundations/accessible-design/patterns&quot;&gt;Material Design 3: Accessible design—Color &amp;amp; Contrast&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, I&amp;#39;ve discussed the accessibility of dark and light themes. As neither is the most accessible choice for all users, you should design and develop your apps to have both. They should either respect the user&amp;#39;s system settings or have an in-app setting for switching between the themes.&lt;/p&gt;
&lt;p&gt;Which do you personally prefer, the light or the dark theme? Do you select them app-by-app (if possible) or use the system-wide setting? And do you implement both for the apps you develop? &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://uxdesign.cc/dark-matters-342ff2c7cc&quot;&gt;Dark mode: How accessible design raises the bar - Layth Sihan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/develop/ui/views/theming/darktheme&quot;&gt;Implement dark theme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/jetpack/compose/designsystems/material3#color-scheme&quot;&gt;Material Design 3 in Compose - Color scheme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@sibel.nalop/dark-theme-switch-in-jetpack-compose-with-compositionlocal-f03bd54683bb&quot;&gt;Dark Theme Switch in Jetpack Compose with CompositionLocal - Sibel Nalop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m3.material.io/foundations/accessible-design/patterns&quot;&gt;Material Design 3: Accessible design - Color &amp;amp; Contrast&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>I&#39;m Still Not One of the Guys</title>
    <link href="https://eevis.codes/blog/2024-03-08/im-still-not-one-of-the-guys/" />
    <updated>2024-03-08T08:17:38.290Z</updated>
    <id>https://eevis.codes/blog/2024-03-08/im-still-not-one-of-the-guys/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/735eX3Y4sEoMhy8L6EP7Pf/0535850cf274b2c03085a5471da9e3d6/wecoded-2024-2__1_.png"/>]]>
      &lt;p&gt;&lt;em&gt;As #WeCoded is a month-long this year, I&amp;#39;ll post a couple of blog posts during this month. This post is the second of these posts.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In 2021, I wrote a blog post called &lt;a href=&quot;https://eevis.codes/blog/2021-07-24/why-im-not-one-of-the-guys/&quot;&gt;&amp;quot;Why I&amp;#39;m Not One of the Guys&amp;quot;&lt;/a&gt; that got a response I didn&amp;#39;t expect. When I published it in Dev, it received many comments about how I was wrong to say that the word &amp;quot;guys&amp;quot; is not gender-neutral. I still disagree, but to debate that is not the point of this blog post.&lt;/p&gt;
&lt;p&gt;Since then, I&amp;#39;ve witnessed so many conversations where someone who does not identify as being part of &amp;quot;guys&amp;quot; asked not to be referred to by that word and got a harsh response. They&amp;#39;re usually told how wrong they are and that they should just suck it up. &lt;/p&gt;
&lt;p&gt;There are a couple of sides to this conversation. People are very protective of the language they&amp;#39;re using - and at the same time, the language we use shapes the reality around us. And then there&amp;#39;s the thing I keep wondering: if someone says they feel excluded, why is the reaction to tell them that they&amp;#39;re wrong and their feelings are not valid? &lt;/p&gt;
&lt;h2 id=&quot;its-about-language&quot;&gt;It&amp;#39;s About Language&lt;/h2&gt;
&lt;p&gt;The words we use matter to us. I&amp;#39;ve had and followed so many conversations where people try to justify the usage of racist, ableist, sexist, homo-, and transphobic words just because they&amp;#39;ve been using those words in the past. And this has happened even with people I know who are all in for equality. &lt;/p&gt;
&lt;p&gt;And it kind of makes sense - language is a massive part of our identity. And if we use words that cause feelings of exclusion, and someone calls us out on that, it can feel like they&amp;#39;re talking about us as persons, not our actions. &lt;/p&gt;
&lt;p&gt;But even a well-meaning person can hurt. I often hear the phrase &amp;quot;Assume good intentions&amp;quot;, and to some extent, I agree. It would be hard to communicate if others weren&amp;#39;t assuming good intentions despite clumsy words. But there&amp;#39;s a certain point where we should consider impact over intent. &lt;/p&gt;
&lt;p&gt;Language holds a lot of power and can shape the reality around us. One great example is the Swedish gender-neutral pronoun &amp;quot;hen&amp;quot; (in addition to &amp;quot;hon&amp;quot;/she and &amp;quot;han&amp;quot;/he). It was (re)introduced to the Swedish vocabulary in 2012, and it has shaped how people think. (ref: &lt;a href=&quot;https://www.pnas.org/doi/10.1073/pnas.1908156116&quot;&gt;Tavits, M. &amp;amp; Pérez, E. O. - Language influences mass opinion toward gender and LGBT equality&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id=&quot;ive-had-to-fight-to-get-here---a-personal-perspective&quot;&gt;I&amp;#39;ve Had to Fight to Get Here - a Personal Perspective&lt;/h2&gt;
&lt;p&gt;You might wonder why this is so important to me. Why just not let it go? &lt;/p&gt;
&lt;p&gt;It&amp;#39;s personal. I&amp;#39;m someone from underrepresented genders in tech and who has faced a lot of biased opinions because of my gender. I&amp;#39;ve seen how big of a role it can play. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve also tried to be one of the guys. I always felt out of place and that I didn&amp;#39;t belong. And now you might mention something about men being different from each other - yes, that&amp;#39;s true. But when it comes to &amp;quot;being one of the guys&amp;quot;, it usually means just a really small subset of things. &lt;/p&gt;
&lt;p&gt;So, I know I&amp;#39;ve been at a disadvantage throughout my career because of my gender and biases towards it. I&amp;#39;ve gone through a lot and don&amp;#39;t want to hide who I am. I want to be recognized for the gender identity I have. And I&amp;#39;m not one of the guys.&lt;/p&gt;
&lt;h2 id=&quot;if-its-exclusive-for-someone-there-are-other-words-to-use&quot;&gt;If It&amp;#39;s Exclusive for Someone, There are Other Words to Use&lt;/h2&gt;
&lt;p&gt;If someone comes to you and has the courage to say that the word you&amp;#39;re using makes them feel excluded, why not just use another word? Good options are &amp;quot;folks,&amp;quot; &amp;quot;all,&amp;quot; &amp;quot;everyone,&amp;quot; or &amp;quot;team,&amp;quot; to name a few. &lt;/p&gt;
&lt;p&gt;And I&amp;#39;m not saying that if there is, for example, a group of women (or other genders) who don&amp;#39;t have any issues with the word &amp;quot;guys&amp;quot;, you can&amp;#39;t use it to describe them. The point is about people who feel excluded by using that word. &lt;/p&gt;
&lt;p&gt;One last thing I want to mention is microaggressions. They&amp;#39;re small actions that communicate some sort of bias towards a minority group. They&amp;#39;re often unintentional (but sometimes intentional), like asking someone where they&amp;#39;re actually from or calling only male employees to the meeting. If you&amp;#39;re interested in reading more, I wrote a blog post &lt;a href=&quot;https://eevis.codes/blog/2023-02-05/sometimes-i-feel-like-im-invisible-experiences-of-a-woman-in-tech/&quot;&gt;&amp;quot;Sometimes I Feel Like I&amp;#39;m Invisible - Experiences of a Woman in Tech&amp;quot;&lt;/a&gt; that explains microaggressions a bit more.&lt;/p&gt;
&lt;p&gt;Microaggressions are something we, who are minorities in tech, face regularly. Personally, it&amp;#39;s often been about actions that subtly suggest that men are better or more important - like being left out of a meeting or when my man-colleague is assumed to know better, even if I&amp;#39;m actually the expert on the topic. Or it&amp;#39;s been about the language where men are the default, like with the word &amp;quot;guys&amp;quot;. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;What am I trying to say with this blog post? The gist could be that if someone comes to you and tells you your word choices are exclusive, please listen to them. They&amp;#39;re not there to criticize you as a person; it&amp;#39;s about the words you use. &lt;/p&gt;
&lt;p&gt;And for those of us who face these situations and feel excluded, we have the right to feel what we feel and to be treated in a way that we&amp;#39;re not excluded. When we&amp;#39;re asking for language that doesn&amp;#39;t exclude us, we are right to do so. Let&amp;#39;s not let anyone tell us otherwise. &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2021-07-24/why-im-not-one-of-the-guys/&quot;&gt;&amp;quot;Why I&amp;#39;m Not One of the Guys&amp;quot;&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pnas.org/doi/10.1073/pnas.1908156116&quot;&gt;Tavits, M. &amp;amp; Pérez, E. O. - Language influences mass opinion toward gender and LGBT equality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-02-05/sometimes-i-feel-like-im-invisible-experiences-of-a-woman-in-tech/&quot;&gt;&amp;quot;Sometimes I Feel Like I&amp;#39;m Invisible - Experiences of a Woman in Tech&amp;quot;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Accessibility Checks with Jetpack Compose Previews</title>
    <link href="https://eevis.codes/blog/2024-03-16/accessibility-checks-with-jetpack-compose-previews/" />
    <updated>2024-03-16T04:38:46.034Z</updated>
    <id>https://eevis.codes/blog/2024-03-16/accessibility-checks-with-jetpack-compose-previews/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1XR2qwBUlmMYL3CGLBlDKq/dc28cfc521ce2cc71f71faceb0b9b2c7/previews-square.png"/>]]>
      &lt;p&gt;Now that &lt;a href=&quot;https://android-developers.googleblog.com/2024/02/android-studio-iguana-is-stable.html&quot;&gt;Android Studio Iguana is out and stable&lt;/a&gt;, I wanted to write about one feature it provides and I&amp;#39;m excited about: Accessibility checks with Compose previews. This feature is part of the new UI checks for Jetpack Compose, and in this blog post, I&amp;#39;ll introduce you to how to use it and provide examples of the issues it finds.&lt;/p&gt;
&lt;h2 id=&quot;compose-ui-checks&quot;&gt;Compose UI Checks&lt;/h2&gt;
&lt;p&gt;UI Checks is a new feature shipped with Iguana. It provides a visual way of auditing your app with different screen sizes, font sizes, dark and light themes, and testing accessibility. It works similarly to how visual linting and accessibility checks integrations work for views. &lt;/p&gt;
&lt;p&gt;Previews have this &amp;quot;Start UI Check Mode&amp;quot;-button at the top right corner:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/BWDOewCIFj7C1FiXbq43f/463bab5efffc180992c8d5dfd571487f/Screenshot_2024-03-13_at_6.02.51.png&quot; alt=&quot;Android Studio&amp;#39;s Preview-UI. The leftmost icon on the top right corner of the preview is selected, and under it is an overlay with the text &amp;#39;Start UI Check Mode&amp;#39;.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After turning the feature on, it runs the checks and shows all the configurations with problems. If no issues are present, the preview UI doesn&amp;#39;t show anything. Here is a picture of what the UI checks mode shows for me from one of the example checks I&amp;#39;ll talk about in a bit:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/9NUTE1ZrTRIusTSWfuyLj/5777d1d51251413e95ca799dae87b055/Screenshot_2024-03-13_at_6.10.02.png&quot; alt=&quot;Overview of the UI Check results displaying different screen width previews, as well as different font sizes. In addition, one of the screens has a dark theme turned on.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It&amp;#39;s an overview picture on purpose, so the quality is not good enough to see the details if you try to zoom in. &lt;/p&gt;
&lt;p&gt;You can find the problems the checker finds in the &amp;quot;Problems&amp;quot;-panel: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/12JmSyrK8bk0F0fmI6hJao/83393f8b53aa0541b69cf7914c33b238/Screenshot_2024-03-13_at_6.23.34.png&quot; alt=&quot;UI Check warnings from Android Studio. There is a warning &amp;#39;Duplicate speakable text present&amp;#39;, and it&amp;#39;s selected. On the right side, there are more details about the problem this warning highlights.&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;accessibility-checks&quot;&gt;Accessibility Checks&lt;/h2&gt;
&lt;p&gt;The accessibility checks the UI checks mode runs are the same as, for example, Accessibility Scanner and Accessibility checks integration for views.&lt;/p&gt;
&lt;p&gt;It checks content labeling, implementation, touch target size, and low contrast categories. You can find more information about each category and its contents in Android Accessibility Help&amp;#39;s article &lt;a href=&quot;https://support.google.com/accessibility/android/answer/6376559&quot;&gt;Accessibility Scanner results&lt;/a&gt;. The contents on that page are pretty much view-related, but each item in the categories has a &amp;quot;learn more&amp;quot; link, and many of them have examples with Compose as well. &lt;/p&gt;
&lt;p&gt;One note, though: I was trying to reproduce some of the problems the checks should catch, and it didn&amp;#39;t flag them. I think I&amp;#39;ve seen these false negatives (and some false positives) with the Accessibility Scanner app, which makes sense as they use the same API. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s dive deeper and discuss some example findings from the accessibility checks and how to fix them. &lt;/p&gt;
&lt;h2 id=&quot;examples&quot;&gt;Examples&lt;/h2&gt;
&lt;h3 id=&quot;duplicate-speakable-text-present&quot;&gt;Duplicate Speakable Text Present&lt;/h3&gt;
&lt;p&gt;The first problem we will look at is &amp;quot;Duplicate Speakable Text Present&amp;quot;. To demonstrate it, I&amp;#39;ve created a small component. It has two rows with a text and a button with text &amp;quot;Read more&amp;quot;. The component is visible in the picture showing where you can turn on the UI Checks.&lt;/p&gt;
&lt;p&gt;The description for the error is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This clickable item&amp;#39;s speakable text: &amp;quot;Read more&amp;quot; is identical to that of 1 other item(s).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why is this a problem? Different assistive technologies and their users rely on content labels to navigate and understand the content. I&amp;#39;ll give two examples of having two (or more!) duplicate clickable items and how they can be a problem.&lt;/p&gt;
&lt;p&gt;First, a user who uses Voice Access for navigation would probably say &amp;quot;Tap Read More&amp;quot; when they want to activate that &amp;quot;Read more&amp;quot; button. But if there is more than one &amp;quot;Read more&amp;quot; button, Voice Access will ask which one. The user would need to pick the one by saying the number with which Voice Access has annotated it. Every time there is a similar situation, it slows the user down and decreases the user experience. Also, it means that they need to speak more to control the device, which might not be a problem when it&amp;#39;s one time, but if that is repeated multiple times, it might become a problem. &lt;/p&gt;
&lt;p&gt;Another example is screen reader users. They often navigate either linearly or use Touch to explore with TalkBack. If the user interface contains multiple elements with the same text label, distinguishing between the elements can be challenging. &lt;/p&gt;
&lt;p&gt;So, how do we fix it? The answer is technically simple but might not be straightforward: The text in clickable elements must differ.&lt;/p&gt;
&lt;p&gt;In this case, a solution could be to change the texts from &amp;quot;Read more&amp;quot; to &amp;quot;Read more about A&amp;quot; and &amp;quot;Read more about B&amp;quot;. I mentioned that the solution may not be straightforward because it touches the copy, and we, as developers, don&amp;#39;t always have control over it.&lt;/p&gt;
&lt;h3 id=&quot;no-speakable-text-present&quot;&gt;No Speakable Text Present&lt;/h3&gt;
&lt;p&gt;The next example I&amp;#39;m giving is an example of no speakable text present. The error description is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This item may not have a label readable by screen readers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One way to reproduce the problem and trigger this warning is to have an image or icon with an empty string in the content description:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Icon(
  ...
  contentDescrption=&amp;quot;&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is that if there is no speakable text present, then there is no text label for that element. Let&amp;#39;s say this is an icon button with just this one icon element as a child. A screen reader user would hear that there is a button, but it doesn&amp;#39;t have a label. They would not know what to do with that button because they wouldn&amp;#39;t see the visual cues. &lt;/p&gt;
&lt;p&gt;Fixing this depends on the icon - if it&amp;#39;s purely decorative (for example, it accompanies an action to illustrate it), the content description can be &lt;code&gt;null&lt;/code&gt;. But if it&amp;#39;s the only thing, for instance, in a button (so, an icon button), it needs to have a descriptive label that communicates the action for the button.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re interested in reading more  about content descriptions, I&amp;#39;ve written a blog post about content descriptions and how to write them: &lt;a href=&quot;https://eevis.codes/blog/2023-11-15/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs/&quot;&gt;How to Add Content Descriptions in Compose - A Guide for Android Devs&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;insufficient-color-contrast-ratio&quot;&gt;Insufficient Color Contrast Ratio&lt;/h3&gt;
&lt;p&gt;The third check is about color contrast. This check is actually twofold - image contrast and text contrast. The error description for the text-related issue is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The item&amp;#39;s text contrast ratio is 4.16. This ratio is based on an estimated foreground color of #8E7098 and an estimated background color of #FFFBFF. Consider using colors that result in a contrast ratio greater than 4.50 for small text, or 3.00 for large text.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, I have a component with regular-sized text on a background, with colors that don&amp;#39;t have enough contrast. &lt;/p&gt;
&lt;p&gt;Why is this a problem? It&amp;#39;s because of visibility. If there is not enough contrast between the background and the text or image, some users might not be able to read the text or see the image. This problem concerns people with different vision-related disabilities and people with cognitive disabilities, to name some groups. Furthermore, it can affect every single user. Have you ever tried using your phone in direct sunlight, and an app didn&amp;#39;t have enough contrast to see the text?&lt;/p&gt;
&lt;p&gt;Images should have a minimum contrast ratio of 3.0:1 between the image and the background. Text, on the other hand, should have a minimum of 3.0:1 for large text and 4.5:1 for small text. You can read more about color contrast and requirements from WebAIM&amp;#39;s article &lt;a href=&quot;https://webaim.org/articles/contrast/&quot;&gt;Contrast and Color Accessibility&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;#39;s common to come across cases where there isn&amp;#39;t enough contrast in the actual themes, especially when the app has both light and dark themes and has not been tested well enough. Some color combinations might work well with light themes, but their dark-theme counterparts don&amp;#39;t have enough color contrast, or vice versa. &lt;/p&gt;
&lt;p&gt;To fix the issue, the contrast between the background and texts or images must be increased. However, increasing the contrast may not be straightforward, as developers don&amp;#39;t always have control over the color choices for the UI elements. &lt;/p&gt;
&lt;h3 id=&quot;touch-target&quot;&gt;Touch Target&lt;/h3&gt;
&lt;p&gt;The final check I&amp;#39;m writing about is for touch targets. The error description is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This item&amp;#39;s height is 35dp. Consider making the height of this touch target 48dp or larger.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this case, I&amp;#39;ve created a button with a fixed height, which is why the touch target size is not high enough. The recommended touch target size minimum is 48dp x 48dp to ensure it&amp;#39;s easier for anyone to press that touchable item. If, for example, a user has tremors in their hands, the bigger the touch target, the easier it is to tap it. &lt;/p&gt;
&lt;p&gt;So, to fix the issue, you&amp;#39;ll need to ensure that the touch target size is at least 48dp in width and 48 dp in height. Material Theme components should provide this out of the box - but if you&amp;#39;re required to create custom implementations of buttons and other elements, remember to make sure they&amp;#39;re at least 48dp x 48dp in size.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked at Compose UI Checks and how they can help with catching some accessibility issues in your app. While they are useful, it&amp;#39;s important to remember that using them does not guarantee that your app is fully accessible. They might render some false positives or negatives, meaning that they either find issues where there are none or don&amp;#39;t find issues that are present. And they catch only a small subset of accessibility issues, so more extensive testing is needed. &lt;/p&gt;
&lt;p&gt;That being said, I&amp;#39;m happy about this new tool for finding accessibility issues during development without any additional tools like Accessibility Scanner (which is a great app!). It&amp;#39;s usually more probable that the tools get used if they don&amp;#39;t need extra effort. &lt;/p&gt;
&lt;p&gt;Have you tested the UI Checks yet? How was it? Did you find something expected or unexpected?&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-developers.googleblog.com/2024/02/android-studio-iguana-is-stable.html&quot;&gt;Android Studio Iguana is out and stable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/accessibility/android/answer/6376559&quot;&gt;Accessibility Scanner results&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-11-15/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs/&quot;&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=&quot;https://webaim.org/articles/contrast/&quot;&gt;Contrast and Color Accessibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Personalizing Accessibility with Settings</title>
    <link href="https://eevis.codes/blog/2024-03-29/personalizing-accessibility-with-settings/" />
    <updated>2024-03-29T06:52:00.880Z</updated>
    <id>https://eevis.codes/blog/2024-03-29/personalizing-accessibility-with-settings/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/4s4JB2hJ8HatkSfq6P8QTr/2cd29d5f5f9e15eadc5276f3880ce40f/accessibility-settings.png"/>]]>
      &lt;p&gt;When users have different, even conflicting, needs for access, what should you do? For example, if one user needs less contrast while the other needs more, how do app developers provide the best experience for all? The truth is that it&amp;#39;s often not possible without some personalization options. &lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;ll share some thoughts about personalization and accessibility, provide ideas on adding personalization options, and discuss setting up dedicated accessibility settings. In the following blog posts, I&amp;#39;ll show concrete ways to personalize your app&amp;#39;s accessibility.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s first examine the possible drawbacks of personalization and then dive deeper into accessibility settings. &lt;/p&gt;
&lt;h2 id=&quot;possible-drawbacks-with-personalization&quot;&gt;Possible Drawbacks with Personalization&lt;/h2&gt;
&lt;p&gt;While I&amp;#39;d love to just say that &amp;quot;Personalization is easy! You should do it!&amp;quot; I recognize that there might be some disadvantages to it. Sometimes, personalization can add more work and complexity to the code, as developers must remember to check if conditions apply for certain things every time they develop a feature. So yes, it adds some additional work.&lt;/p&gt;
&lt;p&gt;But on the other hand, isn&amp;#39;t the goal of an app to be usable for its users? Sometimes, one solution doesn&amp;#39;t fit all, and with personalization we can solve problems with conflicting user needs. We, as developers, sometimes go to great lengths to get some animation or piece of UI just right, which might take as much time as adding personalization options. &lt;/p&gt;
&lt;p&gt;Of course, personalization is not the best approach for every situation. But I would argue that for many apps, providing options for users to personalize their experience would break down access barriers and make their experience more enjoyable, so it&amp;#39;s time well spent. &lt;/p&gt;
&lt;h2 id=&quot;accessibility-settings-as-a-form-of-personalization&quot;&gt;Accessibility Settings as a Form of Personalization&lt;/h2&gt;
&lt;p&gt;One way to give the user control is to add settings to handle the different aspects of the application. Some applications have done this well. For example, Slack has an accessibility section in their application preferences. It&amp;#39;s a bit different for desktop and mobile, but this is how it, at the time of writing, looks like for their Android app:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wn51d77jfflavbnx10vg.png&quot; alt=&quot;Slack&amp;#39;s accessibility settings with five on/off toggles: Allow animated images and emojis, underline links, display typing indicators, raise to listen, and announce huddle calls.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Personally, I&amp;#39;m really grateful for two settings: The animation setting and the underlined links setting. The first one helps me avoid any symptoms, as animations can trigger them. The setting also helps me concentrate. I tested to enable the animations, and phew, there was a lot of movement with the emojis! On the other hand, the second one reduces cognitive load when I can easily see that a link is a link in the middle of a sentence in a message. &lt;/p&gt;
&lt;p&gt;Slack also has an option to change the app&amp;#39;s theme. It&amp;#39;s part of another set of settings, &amp;quot;Look and feel&amp;quot;. The setting is called &amp;quot;Dark mode&amp;quot;, and it has three options: &amp;quot;System default&amp;quot;, &amp;quot;On&amp;quot;, and &amp;quot;Off&amp;quot;. These options are great, as some users might prefer their phone in dark or light mode, but their apps in the other, for example, to help with legibility. &lt;/p&gt;
&lt;p&gt;There are also other apps that provide these three options for Dark mode. For example, Instagram, Duolingo, and Storytel, and probably many others - those were just the apps I first checked. &lt;/p&gt;
&lt;p&gt;Speaking of Duolingo, they also have a dedicated accessibility setting section in their app: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1k2zez6wnltk5496c22l.png&quot; alt=&quot;Duolingo&amp;#39;s accessibility settings. It has four settings with on/off toggles: Speaking exercises, listening exercises, animations, and haptic feedback.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;They also have this animation-related setting, which is nice. However, personally, I think it would&amp;#39;ve been great if the setting wasn&amp;#39;t on by default—or if it was set by default according to the operating system-level setting of removing animation. If you&amp;#39;re now wondering what I&amp;#39;m talking about, I wrote a blog post about the &amp;quot;remove animations&amp;quot;-setting back about a year ago: &lt;a href=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Android, Animations and Reduced Motion&lt;/a&gt; &lt;/p&gt;
&lt;h3 id=&quot;the-form-of-accessibility-settings&quot;&gt;The Form of Accessibility Settings&lt;/h3&gt;
&lt;p&gt;While going through various apps in search of accessibility settings, I noticed that some of the apps include accessibility settings but don&amp;#39;t annotate them as such. The dark mode example is also an excellent illustration of this behavior. In most apps I checked, turning dark mode on was not part of the accessibility settings if the app had a dedicated section. &lt;/p&gt;
&lt;p&gt;I can see this as an advantage and disadvantage. On the other hand, when something is not labeled as an accessibility setting, more people might find it. Many users tend to disregard accessibility settings with the thought, &amp;quot;I don&amp;#39;t need any accessibility settings,&amp;quot; and do not even look at whether some settings would benefit them. But if they&amp;#39;re not labeled as accessibility settings, these users might find them. &lt;/p&gt;
&lt;p&gt;But, and here&amp;#39;s a huge but: Not having the settings labeled as accessibility settings might mean that users who need those settings don&amp;#39;t find them. While I was going through the apps I&amp;#39;m using, I noticed some of them have settings that would belong to accessibility settings, but because they&amp;#39;re hidden in other settings, I probably wouldn&amp;#39;t find them. &lt;/p&gt;
&lt;p&gt;This all comes down to cognitive accessibility: Clear sections with headings help users find what they&amp;#39;re looking for. Sometimes, it might be justifiable to have accessibility-related settings as part of other settings. However, in most cases, accessibility settings should be labeled as accessibility settings to be easy to find. &lt;/p&gt;
&lt;p&gt;Okay, we&amp;#39;ve discussed why accessibility settings are a good idea for your app to personalize the experience. Now, you might wonder how to actually do this. You have multiple implementation options depending on your app&amp;#39;s architecture, goal, and other factors. To help you with the decisions, I&amp;#39;ll first discuss two considerations: supporting operating system-level settings and adding a custom settings section to the app. After that, I&amp;#39;ll talk a bit more about the actual technical decisions. &lt;/p&gt;
&lt;h3 id=&quot;operating-system-level-settings&quot;&gt;Operating System Level Settings&lt;/h3&gt;
&lt;p&gt;Android&amp;#39;s operating system provides a collection of accessibility settings. You can find them in Settings -&amp;gt; Accessibility. From a developer&amp;#39;s point of view, these settings are one of two types: They either work out of the box, or developers need to implement the support for them. &lt;/p&gt;
&lt;p&gt;The first type, out-of-the-box-working settings, don&amp;#39;t usually require (much) work from the developer. A good example is the &amp;quot;Remove animations&amp;quot;-setting, which sets the &lt;code&gt;ANIMATOR_DURATION_SCALE&lt;/code&gt; global constant to 0. Animator-based animations use this same constant, and animations are gone when it&amp;#39;s set to 0. There are some exceptions where this setting is not respected out of the box, and if you&amp;#39;re interested in learning more, the blog post I linked above has more info. Just remember to test that the app works correctly, even without animations.&lt;/p&gt;
&lt;p&gt;For some other accessibility settings, app developers need to explicitly add support. One example is the &amp;quot;Time to take action&amp;quot;-setting, which controls the amount of time the user has to take an action. Android Accessibility Help&amp;#39;s article &amp;quot;&lt;a href=&quot;https://support.google.com/accessibility/android/answer/9426889?hl=en&quot;&gt;Change time to take action&lt;/a&gt;&amp;quot; mentions, &amp;quot;Note: Not all apps support this setting.&amp;quot; This disclaimer is there precisely because it doesn&amp;#39;t work out of the box, and the app developers need to add support for it. If you&amp;#39;re interested in learning more about supporting that setting, I&amp;#39;m planning to write a blog post about it and how to support it in your app.&lt;/p&gt;
&lt;h3 id=&quot;custom-accessibility-settings-for-the-app&quot;&gt;Custom Accessibility Settings for the App&lt;/h3&gt;
&lt;p&gt;The other option would be to add custom accessibility settings for the app. In the previous sections, I&amp;#39;ve discussed in what form these settings should be presented to the user, and I&amp;#39;ve suggested adding a dedicated section for accessibility in the settings. In the following blog posts, I will demonstrate how to integrate these settings into concrete use cases.&lt;/p&gt;
&lt;h3 id=&quot;some-technical-considerations&quot;&gt;Some Technical Considerations&lt;/h3&gt;
&lt;p&gt;Android provides a nice tool for saving settings values: &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/datastore&quot;&gt;Preferences DataStore&lt;/a&gt;. In some cases, saving these settings to a database (like Room or SQLDelight) could be an option. It all depends on your app architecture and what you&amp;#39;re trying to accomplish. &lt;/p&gt;
&lt;p&gt;In my examples, I will use DataStore. There will be a settings screen with controls for different accessibility settings, and a way to toggle the selection on and off, or select from a broader set of options. &lt;/p&gt;
&lt;p&gt;I will demonstrate the technical aspects more in the upcoming blog posts. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, I&amp;#39;ve discussed personalization via accessibility settings, some possible drawbacks (but I would argue they are worth it), and ways to implement and support these settings in your app. While this blog post didn&amp;#39;t go deep into technical aspects, others will follow. In the upcoming weeks (or months, depending on my life), I will at least write a blog post about adding a setting to toggle labels with icons, and adjusting the color scheme to a high-contrast one. &lt;/p&gt;
&lt;p&gt;Do you use accessibility settings in apps? What are the good examples of these settings for you? What about the bad? Or, as a developer, have you ever implemented such settings?&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Android, Animations and Reduced Motion&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/accessibility/android/answer/9426889?hl=en&quot;&gt;Change time to take action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/datastore&quot;&gt;Preferences DataStore&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Toggle Labels With Icons - Personalizing Accessibility</title>
    <link href="https://eevis.codes/blog/2024-04-21/toggle-labels-with-icons-personalizing-accessibility/" />
    <updated>2024-04-21T06:11:56.182Z</updated>
    <id>https://eevis.codes/blog/2024-04-21/toggle-labels-with-icons-personalizing-accessibility/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1ABJrMHIYMHXNfNgPbJKds/cf190e1a1ab78b4863b85c72f1b1a0ba/accessibility-settings-labels-square.png"/>]]>
      &lt;p&gt;As mentioned in my previous blog post, &lt;a href=&quot;https://eevis.codes/blog/2024-03-29/personalizing-accessibility-with-settings/&quot;&gt;Personalizing Accessibility with Settings&lt;/a&gt;, accessibility for the broadest group of users often requires some personalization. What is accessible for one might not be accessible for another. &lt;/p&gt;
&lt;p&gt;In this blog post, I will discuss one example of accessibility issues that could be solved with the help of settings: Icons and the need for text labels accompanying them. As you can see from the next section, this particular issue is personal to me, and I wanted to give an example of how to solve it. &lt;/p&gt;
&lt;h2 id=&quot;why&quot;&gt;Why?&lt;/h2&gt;
&lt;p&gt;Icons are not universal. I can&amp;#39;t tell you how many times I&amp;#39;ve opened an app, seen icons (without labels), and wondered what the heck they mean. Where do they lead me? What happens if I press the button with an icon? Do I get to the place I&amp;#39;m looking for? Or does my phone explode? Who knows. The UI is not communicating it to me. &lt;/p&gt;
&lt;p&gt;I know UX people say that it&amp;#39;s about learning. &amp;quot;If you just use the app for long enough, you&amp;#39;ll learn&amp;quot;. But the thing is, I&amp;#39;ve had a TBI, which has affected... You guessed right, my memory. So, I find it super frustrating when there are all kinds of symbols I don&amp;#39;t understand without text labels, and I have no idea what to do with them. These apps make me frustrated. And that&amp;#39;s not the goal of an app, right?&lt;/p&gt;
&lt;p&gt;On the other hand, I know some people benefit from not having those labels present. Also, almost every designer I&amp;#39;ve talked to about this topic seemed to think that having text in the UI is ugly (yes, I&amp;#39;m exaggerating a bit.) So, I&amp;#39;m coming up with a solution to help both groups - those who need the labels and those who don&amp;#39;t need them: Personalization — a setting in the app to toggle showing labels with icons. &lt;/p&gt;
&lt;h2 id=&quot;how&quot;&gt;How?&lt;/h2&gt;
&lt;p&gt;This section will present one way to add this toggle in settings. It&amp;#39;s not a complete solution, as the actual implementation varies based on your app&amp;#39;s architectural patterns. It aims to provide guidelines on how to do things by showing one example. &lt;/p&gt;
&lt;h3 id=&quot;settings-data-store&quot;&gt;Settings Data Store&lt;/h3&gt;
&lt;p&gt;The first step is to add settings where the user can toggle whether labels are shown in the app UI. Adding settings has two parts: the settings screen and storing the setting value. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s start with the latter. I&amp;#39;m using &lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/datastore#preferences-datastore&quot;&gt;Preferences DataStore&lt;/a&gt; in the example app I&amp;#39;m building, as it&amp;#39;s perfect for storing simple data, such as setting values. Let&amp;#39;s first add the dependency (check the latest version from the link):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// build.gradle.kts

dependencies {
    implementation(&amp;quot;androidx.datastore:datastore-preferences:1.1.0&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, the data store needs to be initialized. As the example app uses dependency injection with Hilt, let&amp;#39;s do that in a &lt;code&gt;SettingsDataStoreModule&lt;/code&gt; where we define the datastore provided for Hilt. &lt;/p&gt;
&lt;p&gt;First, let&amp;#39;s initialize the data store: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsDataStoreModule.kt

private val Context.datastore: DataStore&amp;lt;Preferences&amp;gt; 
    by preferencesDataStore(&amp;quot;settings&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then provide it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsDataStoreModule.kt

@Singleton
@Provides
fun provideSettingsDatastore(
    @ApplicationContext context: Context
): DataStore&amp;lt;Preferences&amp;gt; = context.datastore
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that the data store has been initialized and can be accessed via dependency injection, we need to add the actual setting storing. In this example application, we&amp;#39;re reading the values from the data store in the repository to make this demo straightforward.&lt;/p&gt;
&lt;p&gt;The data store is injected into &lt;code&gt;SettingsRepository&lt;/code&gt;, where we define the flow the app will observe and a function to toggle the value. First, let&amp;#39;s define the key for storing the setting value for showing the labels:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsRepository.kt

private object PreferencesKeys {
    val showLabels = booleanPreferencesKey(&amp;quot;show_labels&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, let&amp;#39;s define the flow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsRepository.kt

val showLabelsFlow: Flow&amp;lt;Boolean&amp;gt; = dataStore.data
    .map {
        it[PreferencesKeys.showLabels] ?: true
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We read the data from the data store and get the &lt;code&gt;showLabels&lt;/code&gt;-preference. If the value from the data store is null, we set the default to true, like the first time the value is accessed. Why true, you might ask? Well, it&amp;#39;s both a personal choice, but it&amp;#39;s also because I believe that the more accessible value should be the default because there is a lot of power with the default. If you want to learn more about that theme, I&amp;#39;ve written a blog post: &lt;a href=&quot;https://eevis.codes/blog/2023-08-07/the-power-of-default/&quot;&gt;The Power of Default&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There&amp;#39;s one more thing: The user must be able to toggle the value. Let&amp;#39;s add a function for that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsRepository.kt

suspend fun toggleShowLabels() {
    dataStore.edit { preferences -&amp;gt;
        val oldVal = preferences[PreferencesKeys.showLabels]
        preferences[PreferencesKeys.showLabels] = 
            (oldVal ?: true).not()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&amp;#39;s a suspend function, as the data store&amp;#39;s &lt;code&gt;edit&lt;/code&gt; function takes in a &lt;code&gt;transform&lt;/code&gt; suspend function, where the values can be updated. We then get the old value and set the opposite boolean as the value - so, if the old value is true, then false, and vice versa. Here again, we use &lt;code&gt;true&lt;/code&gt; as the default value if &lt;code&gt;oldVal&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;settings-in-viewmodel&quot;&gt;Settings in &lt;code&gt;ViewModel&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The next place in the flow is the ViewModel, which is used to get the stored values to the UI. Again, to keep this simple, we&amp;#39;re using as simple a ViewModel as possible, and your project&amp;#39;s implementation might differ. &lt;/p&gt;
&lt;p&gt;As I&amp;#39;m using Hilt in my project, &lt;code&gt;SettingsRepository&lt;/code&gt; is injected into the &lt;code&gt;SettingsViewModel&lt;/code&gt; so the function and flow we defined in the previous section are available. Let&amp;#39;s first define the state variables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsViewModel.kt

private var _showLabelsEnabled = MutableStateFlow(true)
val showLabelsEnabled = _showLabelsEnabled.asStateFlow()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we add a function to collect the value from &lt;code&gt;showLabelsFlow&lt;/code&gt; and call it in the view model&amp;#39;s &lt;code&gt;init&lt;/code&gt;-function. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsViewModel.kt

init {
  getShowLabelsEnabled()
}

private fun getShowLabelsEnabled() {
    viewModelScope.launch {
        settingsRepository.showLabelsFlow.collect {
            _showLabelsEnabled.value = it
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we add a function to toggle the setting and use the function from the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsViewModel.kt

fun toggleShowLabelsEnabled() {
    viewModelScope.launch {
        settingsRepository.toggleShowLabels()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All right, the next thing to do is add the actual UI and use these state values and functions we defined. &lt;/p&gt;
&lt;h3 id=&quot;settings-screen&quot;&gt;Settings Screen&lt;/h3&gt;
&lt;p&gt;The settings screen is simple. At this time, there is only one switch to toggle the setting value: &lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/52KU3DR0iGp4PXbDXCuQNH/39815047a2ac137ce92bf55dd5d7f900/settings.png&quot; alt=&quot;Settings screen, which has toolbar with Close-icon at the top left corner, and text &#39;Settings&#39; next to it. Under the toolbar there&#39;s a title &#39;Settings&#39;, and under it, a switch with label &#39;Show labels&#39;.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;I won&amp;#39;t go through the whole code for the screen here, but I want to point out one accessibility detail from it. When adding a switch, it needs a couple of lines of modifier magic to make it more accessible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsScreen.kt

@Composable
fun ToggleSwitch(
    checked: Boolean,
    label: String,
    toggleValue: () -&amp;gt; Unit
) {
    Row(
        modifier = Modifier
            ...
            .toggleable(
                value = checked,
                role = Role.Switch,
                onValueChange = {
                    toggleValue()
                }
            ),
    ) {
        Text(label)
        Switch(
            modifier = Modifier.clearAndSetSemantics {},
            checked = checked,
            onCheckedChange = { toggleValue() },
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&amp;#39;ve left some unrelated code out, but the main point is that the text label is not associated with the switch for Accessibility APIs unless we wrap them both with a &lt;code&gt;Row&lt;/code&gt; (or other layout component), add a &lt;code&gt;toggleable&lt;/code&gt; modifier for it, and then set &lt;code&gt;clearAndSetSemantics&lt;/code&gt; for the &lt;code&gt;Switch&lt;/code&gt; component. You can read more about why in a blog post I wrote: &lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;Improving Android Accessibility with Modifiers in Jetpack Compose&lt;/a&gt;. &lt;/p&gt;
&lt;h3 id=&quot;check-for-settings-value&quot;&gt;Check for Settings Value&lt;/h3&gt;
&lt;p&gt;Okay, now that we have added the ability to toggle the setting, the final thing is to use the setting value in our UI. What we are building here is a simple UI, which has a couple of message cards for the internet that look like this:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/4UNTq5d1WJkU9Njf9s6tge/ddd1f7e6855c10247c193c6af65e2f8f/cards.png&quot; alt=&quot;A screen with title Messages to Internet. It has three cards: Trans women are women with content Whatever the others say, trans women are women and trans men are men, Right to safe abortion is a human right with content If abortions are illegal, they won&#39;t stop - they just become dangerous and There are no self-made millionaires with visible content of They have access to generational wealth. Each of the card has two icon buttons: One with a pen icon and another with a trash can icon.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;As you might have guessed, we want to toggle showing labels for the pen- and trash can icon buttons. The screen uses the same view model as the Settings screen (&lt;code&gt;SettingsViewModel&lt;/code&gt;) for simplicity. In a non-demo application, each feature would most likely have its own view model. &lt;/p&gt;
&lt;p&gt;So, there&amp;#39;s the &lt;code&gt;CardsScreen&lt;/code&gt; to which we&amp;#39;re passing the view model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardsScreen.kt

@Composable
fun CardsScreen(viewModel: SettingsViewModel) {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside &lt;code&gt;CardsScreen&lt;/code&gt;, we want to collect the &lt;code&gt;showLabelsEnabled&lt;/code&gt; value from the view model as a state:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardsScreen.kt

val showLabels = settingsViewModel
    .showLabelsEnabled
    .collectAsState()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, we want to use that value to determine if we&amp;#39;re showing the labels or not. The icons in the picture above are &lt;code&gt;IconButton&lt;/code&gt;s, and we want to use &lt;code&gt;OutlinedButton&lt;/code&gt;s for the labeled ones. To make the code more modular, there are different components for buttons. For the labeled version, we have the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardsScreen.kt

@Composable
fun ButtonsWithLabels() {
    Row(...)
    ) {
        OutlinedButton(onClick = { /*TODO*/ }) {
            ButtonContent(
                text = &amp;quot;Edit&amp;quot;,
                icon = Icons.Filled.Edit
            )
        }
        OutlinedButton(onClick = { /*TODO*/ }) {
            ButtonContent(
                text = &amp;quot;Delete&amp;quot;,
                icon = Icons.Filled.Delete
            )
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;code&gt;ButtonContent&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardsScreen.kt

@Composable
fun ButtonContent(text: String, icon: ImageVector) {
    Row(...) {
        Icon(
            imageVector = icon,
            contentDescription = null,
            tint = MaterialTheme.colorScheme.primary
        )
        Text(text)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code for the non-labeled version is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardsScreen.kt

@Composable
fun ButtonsWithoutLabels() {
    Row(...) {
        IconButton(onClick = { /*TODO*/ }) {
            Icon(
                imageVector = Icons.Filled.Edit,
                contentDescription = &amp;quot;Edit&amp;quot;,
                tint = MaterialTheme.colorScheme.primary
            )
        }
        IconButton(onClick = { /*TODO*/ }) {
            Icon(
                imageVector = Icons.Filled.Delete,
                contentDescription = &amp;quot;Delete&amp;quot;,
                tint = MaterialTheme.colorScheme.primary
            )
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main difference (apart from the labels) is that the icons&amp;#39; content descriptions are null for buttons with labels, as the text is part of the button content and doesn&amp;#39;t need to be repeated. &lt;/p&gt;
&lt;p&gt;Next, let&amp;#39;s check the value of &lt;code&gt;enableLabels&lt;/code&gt; and set the component accordingly: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardsScreen.kt

if (showLabels)  
    ButtonsWithLabels() 
else 
    ButtonsWithoutLabels()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&amp;#39;s it. When the setting is toggled on, the cards look like this:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1hosQ8pdQA3CZROgAbb30S/d4f655f8279f4e1ec427326d32629c95/cards-with-labels.png&quot; alt=&quot;The same screen as before, but each of the icon buttons has now labels accompanying them. &quot; class=&quot;portrait-img&quot; /&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked at one way to store an app&amp;#39;s accessibility settings and personalize the UI accordingly. The examples have been, well, examples to demonstrate ideas, and on a production-level app, you probably have a bit more fine-tuned setup. &lt;/p&gt;
&lt;p&gt;The next blog post about accessibility and personalization will be about adjusting the app&amp;#39;s theme by providing an option for selecting either the system default theme, dark theme, light theme, or a high-contrast theme.&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-03-29/personalizing-accessibility-with-settings/&quot;&gt;Personalizing Accessibility with Settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/datastore#preferences-datastore&quot;&gt;Preferences DataStore&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-08-07/the-power-of-default/&quot;&gt;The Power of Default&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;Improving Android Accessibility with Modifiers in Jetpack Compose&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Change App Theme - Personalizing Accessibility</title>
    <link href="https://eevis.codes/blog/2024-05-21/change-app-theme-personalizing-accessibility/" />
    <updated>2024-05-21T03:12:59.413Z</updated>
    <id>https://eevis.codes/blog/2024-05-21/change-app-theme-personalizing-accessibility/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3Vb4VJXBE89nAXU4fFoK8H/526c1becd1c434e20e96850bceca9259/accessibility-settings-theme-square.png"/>]]>
      &lt;p&gt;I&amp;#39;m continuing the theme of personalizing accessibility, which I&amp;#39;ve started with my two posts. You can find them from the following links: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-03-29/personalizing-accessibility-with-settings&quot;&gt;Personalizing Accessibility with Settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-04-21/toggle-labels-with-icons-personalizing-accessibility&quot;&gt;Toggle Labels With Icons — Personalizing Accessibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a quick recap, in the previous posts, I&amp;#39;ve discussed how personalization can generally be the key to improving accessibility, and given a concrete example of adding a setting to hide or display labels with icons. &lt;/p&gt;
&lt;p&gt;In this blog post, I will continue with concrete examples. We will add a setting to change the app&amp;#39;s theme. The user will have four options: follow system settings (the default), light, dark, and high-contrast color theme. Let&amp;#39;s dive in.&lt;/p&gt;
&lt;h2 id=&quot;why&quot;&gt;Why?&lt;/h2&gt;
&lt;p&gt;Sometimes, users want to use their apps with a different theme than what their phone has as its theme. Personally, I use everything in dark mode, but I&amp;#39;ve encountered some apps with dark themes with colors that are unusable for me. They either have too much contrast, or sometimes, too little. In those cases, I wanted to change to a light theme. Usually, there has been no such option, so I&amp;#39;ve either abandoned the app or used it as little as possible if I needed to use it. &lt;/p&gt;
&lt;p&gt;So it&amp;#39;s all about giving users the control. The minimum options for this type of setting would be the system default, light, and dark. But what about the high-contrast theme? Why would you want to add that? Who even needs it?&lt;/p&gt;
&lt;h3 id=&quot;high-contrast-theme&quot;&gt;High-Contrast Theme&lt;/h3&gt;
&lt;p&gt;A high-contrast theme has a limited color palette with higher contrast colors. One concrete example is Windows High-Contrast Mode, which was developed for Windows 7 and later. Android provides a setting for high-contrast text, but it only applies to text, not graphics. &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://webaim.org/projects/lowvisionsurvey2/#contrastMode&quot;&gt;WebAIM&lt;/a&gt; surveyed low-vision users in 2018, showing that 51.4% of the respondents (n=248) used a high-contrast mode. Low-vision users are a large group needing high-contrast themes, but there are others as well. For example, people with migraines, Irlene syndrome, or some people with dyslexia might benefit from high-contrast themes. High-contrast modes can be useful in direct sunlight for anyone, too. &lt;/p&gt;
&lt;h2 id=&quot;how&quot;&gt;How?&lt;/h2&gt;
&lt;p&gt;The flow for adding a possibility to change the app&amp;#39;s theme is similar to the labels and icons in the previous blog post: We add a setting to the settings screen, store the value in a data store, and then use that value in the app to decide which theme to display. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s start with the settings screen. We want to add a section for selecting the theme and options to choose from. The list contains four options: System default, dark theme, light theme, and high-contrast theme. In the example, the colors are shown:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/4ZQdrBEUeOnw2CWjxBRwZ8/0c840397cf458e551b180a51ccd4d69e/Screenshot_20240512_065951.png&quot; alt=&quot;Settings screen, which has a section for color settings. Each of the setting is a box with a title, and displays three colors the theme uses. Themes are system default, dark theme, light theme and high-contrast theme. Dark theme is selected.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;h3 id=&quot;storing-the-data&quot;&gt;Storing the Data&lt;/h3&gt;
&lt;p&gt;First, continuing with the setup we had in the previous blog post, we want to add a key-value pair to the data store we were using for storing the settings. As mentioned in the previous blog post, for the sake of straightforwardness for the blog posts, the data store is defined and interacted with in the &lt;code&gt;SettingsRepository&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;As we have four options to choose from, a simple boolean value does not work in this case. In the code, we want to use predefined values for themes, so we create an enum class for the theme with all the options and use null for the system default.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// ThemeExt.kt

enum class Theme {
    Dark,
    Light,
    HighContrast;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The data store can&amp;#39;t store enum values, so we need to have a way to convert the enums to strings and back. The to string-part is easy - we can just use the &lt;code&gt;toString()&lt;/code&gt;-function. For the other conversion, we need to define a helper function. Let&amp;#39;s add it to the enum class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;enum class Theme {
    ...

    companion object {
        fun from(value: String?): Theme? {
            return when (value) {
                Dark.name -&amp;gt; Dark
                Light.name -&amp;gt; Light
                HighContrast.name -&amp;gt; HighContrast
                else -&amp;gt; null
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this method, we check which of the theme&amp;#39;s names matches the value we&amp;#39;re passing in. Also, we have the default case of null - if nothing matches, we assume it&amp;#39;s the system default and set the value to null. &lt;/p&gt;
&lt;p&gt;Okay, now we have everything to store the theme in the data store. First, let&amp;#39;s add a flow to read the value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsRepository.kt

private object PreferencesKeys {
    ...
    val colorTheme = stringPreferencesKey(&amp;quot;color_theme&amp;quot;)
}

val colorThemeFlow: Flow&amp;lt;Theme?&amp;gt; = dataStore.data
    .map {
        Theme.from(it[PreferencesKeys.colorTheme])
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, we first add a preferences key to the preferences key object. For the flow, we read the value from the data store, and then use &lt;code&gt;Theme.from()&lt;/code&gt;, which we defined earlier, to parse the string to Theme-enum. &lt;/p&gt;
&lt;p&gt;For editing the value, we define a function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsRepository.kt

suspend fun setColorScheme(theme: Theme?) {
    dataStore.edit { preferences -&amp;gt;
        preferences[PreferencesKeys.colorTheme] = theme.toString()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the function, we set the string value of the enum to the data store using the preference key we defined. &lt;/p&gt;
&lt;p&gt;The next thing to do is to update the &lt;code&gt;SettingsViewModel&lt;/code&gt;. First, we add a mutable state flow to store the theme&amp;#39;s value and use it in the UI:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsViewModel.kt

private var _colorScheme = 
    MutableStateFlow&amp;lt;Theme?&amp;gt;(Theme.Dark)
val colorScheme = _colorScheme.asStateFlow()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we define functions to read the value from the repository and use the repository&amp;#39;s &lt;code&gt;setColorScheme&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsViewModel.kt

private fun getColorScheme() {
    viewModelScope.launch {
        settingsRepository.colorThemeFlow.collect {
            _colorScheme.value = it
        }
    }
}

fun setColorScheme(theme: Theme?) {
    viewModelScope.launch {
        settingsRepository.setColorScheme(theme)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last thing is to call &lt;code&gt;getColorScheme()&lt;/code&gt; in the init block so that we get the initial value when opening the app:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsViewModel.kt

init {
    ...
    getColorScheme()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;settings-screen&quot;&gt;Settings Screen&lt;/h3&gt;
&lt;p&gt;As with the last post, I&amp;#39;m not going to demonstrate how to use the values from the &lt;code&gt;SettingsViewModel&lt;/code&gt; but I&amp;#39;m just going to mention that from accessibility and semantics point of view, there are a couple of things to note: Each of the color options need to have &lt;code&gt;selectable&lt;/code&gt;-modifier, and the component wrapping the color options needs to have a &lt;code&gt;selectableGroup()&lt;/code&gt;-modifier. How these look like in my code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsScreen.kt 

@Composable
fun ColorOption(
    option: ColorOptionData, 
    isCurrentTheme: Boolean, 
    setColorScheme: (Theme?) -&amp;gt; Unit
) {
    ...

    Row(
        modifier = Modifier.selectable(
        selected = isCurrentTheme, 
        role = Role.RadioButton
    ) {
        setColorScheme(option.theme)
    },
        ...
    ) {
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the parent component, we have the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// SettingsScreen.kt 

Column(
    modifier = Modifier.selectableGroup(),
) {
    colorOptions.map { option -&amp;gt;
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to know more about why this is done, my blog post &lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;Improving Android Accessibility with Modifiers in Jetpack Compose&lt;/a&gt; explains the reasons. &lt;/p&gt;
&lt;h3 id=&quot;changing-the-theme-for-ui&quot;&gt;Changing the Theme for UI&lt;/h3&gt;
&lt;p&gt;The final step is to use the stored value for the theme and update it accordingly. We&amp;#39;ll need to add logic to set the theme according to the stored value.&lt;/p&gt;
&lt;p&gt;Note that the actual theme definition is out of scope for this blog post - I have colors defined for dark, light, and high-contrast themes, and I&amp;#39;m going to use them in the code. &lt;/p&gt;
&lt;p&gt;In &lt;code&gt;Theme.kt&lt;/code&gt;, let&amp;#39;s add additional checks to the default implementation of the application theme:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// Theme.kt

@Composable
fun AppTheme(
    useDarkTheme: Boolean = isSystemInDarkTheme(),
    theme: Theme? = null,
    content: @Composable () -&amp;gt; Unit,
) {
    val colors = when (theme) {
        Theme.Dark -&amp;gt; DarkColors
        Theme.Light -&amp;gt; LightColors
        Theme.HighContrast -&amp;gt; HighContrast
        null -&amp;gt; if (!useDarkTheme) {
            LightColors
        } else {
            DarkColors
        }
    }
    MaterialTheme(
        colorScheme = colors,
        content = content,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We add an optional parameter of type &lt;code&gt;Theme&lt;/code&gt; to the &lt;code&gt;AppTheme&lt;/code&gt; composable and then use it to decide which colors we&amp;#39;re setting to the &lt;code&gt;MaterialTheme&lt;/code&gt; component. If the theme is not null, then we set the colors per the value, and if it&amp;#39;s null, then we should use the system&amp;#39;s default colors. For this, we check the &lt;code&gt;isSystemInDarkTheme&lt;/code&gt; value and set the colors accordingly. &lt;/p&gt;
&lt;p&gt;Finally, in &lt;code&gt;MainActivity&lt;/code&gt;, where our app&amp;#39;s root is defined, we read the value of the stored theme and then pass it to the &lt;code&gt;AppTheme&lt;/code&gt;-composable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainActivity.kt

val theme = settingsViewModel.colorScheme.collectAsState()

AppTheme(theme = theme.value) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with these changes, we will get the following themes visible when they&amp;#39;re selected from the settings:&lt;/p&gt;
&lt;div class=&quot;img-row&quot;&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1DnS9fckLFOBD2vk9qSPUM/00d7cfbce3e7ec16f710c818784bfccf/Screenshot_20240520_063853.png&quot; alt=&quot;The app screen with dark theme, containing cards with titles: &#39;Trans women are women&#39;, &#39;Right to a safe abortion is a human right&#39; and &#39;There are no self-made millionaires&#39;.&quot; class=&quot;portrait-img&quot; /&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/7bYQXIYBdjuBIp8HUScQbJ/0adafbcc676c9dbc93596e4b42437cdc/Screenshot_20240520_063910.png&quot; alt=&quot;The app screen with light theme, containing cards with titles: &#39;Trans women are women&#39;, &#39;Right to a safe abortion is a human right&#39; and &#39;There are no self-made millionaires&#39;.&quot; class=&quot;portrait-img&quot; /&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/cn7FnRQwf4FnRPKBD0zJJ/e686984f7d93ea605b45a2cab9c24437/Screenshot_20240520_063930.png&quot; alt=&quot;The app screen with high-contrast theme, containing cards with titles: &#39;Trans women are women&#39;, &#39;Right to a safe abortion is a human right&#39; and &#39;There are no self-made millionaires&#39;.&quot; class=&quot;portrait-img&quot; /&gt;
&lt;/div&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked at how to let users decide which theme they want to use in the app by adding a setting for it. The provided themes are either light, dark, or high-contrast themes - or follow the system default theme. &lt;/p&gt;
&lt;p&gt;Have you implemented a theme selector in your app? Have you encountered the high-contrast theme before? Do you have ideas for which type of accessibility settings I could cover next?&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-03-29/personalizing-accessibility-with-settings&quot;&gt;Personalizing Accessibility with Settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-04-21/toggle-labels-with-icons-personalizing-accessibility&quot;&gt;Toggle Labels With Icons — Personalizing Accessibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webaim.org/projects/lowvisionsurvey2/#contrastMode&quot;&gt;WebAIM&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;Improving Android Accessibility with Modifiers in Jetpack Compose&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title> Accessibility Tests  in Compose - Name, Role, Value</title>
    <link href="https://eevis.codes/blog/2024-06-03/accessibility-tests-in-compose-name-role-value/" />
    <updated>2024-06-03T03:31:45.745Z</updated>
    <id>https://eevis.codes/blog/2024-06-03/accessibility-tests-in-compose-name-role-value/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1SPayePHlWbb2zrtmoMnyD/434ffaea2556fe00907fc651cd5f8063/accessibility-tests-name-role-value.png"/>]]>
      &lt;p&gt;When writing tests for your app, you should also consider testing for accessibility-related things. And I get it; it can be challenging to know where to start. So, I decided to write this blog post about how to test some accessibility aspects. &lt;/p&gt;
&lt;p&gt;In this post, we will add some accessibility-related tests for three custom components constructed with the help of &lt;code&gt;clickable&lt;/code&gt;, &lt;code&gt;selectable&lt;/code&gt;, and &lt;code&gt;toggleable&lt;/code&gt; modifiers. These components were built in a blog post I wrote: &lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;Improving Android Accessibility with Modifiers in Jetpack Compose&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-are-we-testing&quot;&gt;What Are We Testing?&lt;/h2&gt;
&lt;p&gt;The tests we&amp;#39;re writing verify that the components have names, roles, and values. But where does this group come from? The background is that Web Content Accessibility Guidelines (WCAG) has a success criterion, &amp;quot;&lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html&quot;&gt;Name, Role, Value&lt;/a&gt;&amp;quot;, which ensures that every element has a programmatically determinable name and role. Also, states, properties, and values that users can change are programmatically changeable. &lt;/p&gt;
&lt;p&gt;And now, if you wonder why I&amp;#39;m mentioning something named &amp;quot;Web,&amp;quot; the WCAG is also used to determine the minimum level of accessibility for mobile apps as well, despite the name. &lt;/p&gt;
&lt;p&gt;Name, in this case, means the accessible name—so, the textual representation of the element. It can be, for example, a button&amp;#39;s text, an icon button&amp;#39;s content description, a label for a switch, or similar. It&amp;#39;s what anyone using a screen reader hears. Voice access users use it to activate interactive elements. &lt;/p&gt;
&lt;p&gt;Role, on the other hand, is the role of the element. It can be, for example, a button - which tells the user that, hey, this is a button, and it should behave as a button. A role is a promise of how things should work, so if you add a role, be sure to add the correct interactions as well. However, roles are used less on Android than on the web. &lt;/p&gt;
&lt;p&gt;Value can refer to an element&amp;#39;s state, property, or value. The exact thing is different per element. For example, with a checkbox, the value tells if it is checked, or with an accordion, it&amp;#39;s the state that tells if it&amp;#39;s opened or closed. &lt;/p&gt;
&lt;p&gt;In the next section, we&amp;#39;ll examine concrete examples of how to test the &amp;quot;Name, Role, Value&amp;quot; success criterion for a couple of custom components mentioned in the intro.&lt;/p&gt;
&lt;h2 id=&quot;writing-the-tests-an-example&quot;&gt;Writing the Tests, an Example&lt;/h2&gt;
&lt;p&gt;As mentioned in the beginning, these tests are written for components for a blog post I&amp;#39;ve written previously. We&amp;#39;ll look into how to test three components: A switch, a radio button group, and a clickable row. &lt;/p&gt;
&lt;p&gt;As the components in the blog posts were simplified for the sake of an example, these tests are also streamlined. With production-grade code, you usually have a bit more sophisticated strategies for, for example, finding the components that are being tested.  &lt;/p&gt;
&lt;h3 id=&quot;toggleable&quot;&gt;Toggleable&lt;/h3&gt;
&lt;p&gt;The first component we&amp;#39;re testing is a switch like in the picture: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/6WzysUn3rGCcGirnIoFR4m/60ea5cde87aafdea3a6c6e901eef92db/Screenshot_2023-07-10_at_8.58.12.png&quot; alt=&quot;A pink rectangle with round corners, and it has the text &amp;quot;Toggleable&amp;quot; at the start (left) side of the row and a switch toggle at the end (right side) of the row.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We want to test three things: First, we want to ensure the component has an accessible name (so, the label of the switch). Second, the role should be correct—it should be a toggleable component. Third, the value should be correct before and after toggling the switch, so whether the switch is on or off. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s write a test: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class ToggleableTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun hasRoleNameValue() {
        composeTestRule.setContent {
            ModifiersExampleTheme {
                ToggleableScreen()
            }
        }

        val toggleableElement = 
            composeTestRule.onNode(hasTestTag(&amp;quot;accessible-toggle&amp;quot;))

        // Assert accessible name
        toggleableElement.assertTextEquals(&amp;quot;Toggleable&amp;quot;)
        // Assert role
        toggleableElement.assertIsToggleable()
        // Assert value
        toggleableElement.assertIsOff()
        toggleableElement.performClick()
        toggleableElement.assertIsOn()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, the test needs a setup, so we need things like the &lt;code&gt;composeTestRule&lt;/code&gt; and setting the content. Then we get the testable component with a test tag &lt;code&gt;accessible-toggle&lt;/code&gt;. Finally, we have the tests for name, role, and value. &lt;/p&gt;
&lt;p&gt;The test for checking the name is straightforward: We want to ensure that the element&amp;#39;s text content equals the word on the label. We can assert that with &lt;code&gt;assertTextEquals&lt;/code&gt;. To test the role, we can use a useful assert function, &lt;code&gt;assertIsToggleable&lt;/code&gt;. Finally, to check if the value (so, the checked state) is correct, we can also use the utility functions &lt;code&gt;assertIsOff&lt;/code&gt; and &lt;code&gt;assertIsOn&lt;/code&gt; and, for toggling the state, &lt;code&gt;performClick&lt;/code&gt;.  &lt;/p&gt;
&lt;h3 id=&quot;selectable&quot;&gt;Selectable&lt;/h3&gt;
&lt;p&gt;The next component we&amp;#39;re testing is a radio button group, as seen in the picture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/QOF0xGo0R4jgbzEc3Iz1Y/7b7cccb2c8a38424738c903389645046/Screenshot_2023-07-10_at_8.58.00.png&quot; alt=&quot;Two rows with pink background, text on the left, and a radio button on the right. The first one is selected and has the text &amp;quot;Option A,&amp;quot; and the second one is not and has the text &amp;quot;Option B&amp;quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For this component, we ensure that both of the options have a name (so, the labels &amp;quot;Option A&amp;quot; and &amp;quot;Option B&amp;quot;), role as &lt;code&gt;selectable&lt;/code&gt;, and value if the item is selected. &lt;/p&gt;
&lt;p&gt;The test for this component is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class SelectableTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Before
    fun setup() {
        composeTestRule.setContent {
            ModifiersExampleTheme {
                SelectableScreen()
            }
        }
    }

    @Test
    fun hasRoleNameValue() {
        val selectableElements = 
            composeTestRule.onAllNodes(hasTestTag(&amp;quot;accessible-selectable&amp;quot;))

        // Assert accessible name
        selectableElements[0].assertTextEquals(&amp;quot;Option A&amp;quot;)
        selectableElements[1].assertTextEquals(&amp;quot;Option B&amp;quot;)
        // Assert role
        selectableElements.assertAll(isSelectable())
        // Assert value
        selectableElements[0].assertIsSelected()
        selectableElements[1].performClick()
        selectableElements[0].assertIsNotSelected()
        selectableElements[1].assertIsSelected()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The structure is very similar to the previous test; first, the setup, then getting the elements, and then asserting name, role, and value. We&amp;#39;re using the same &lt;code&gt;assertTextEquals&lt;/code&gt; to check the elements&amp;#39; labels (so, names). Similarly to the &lt;code&gt;toggleable&lt;/code&gt;, there are functions for asserting the role and values for the &lt;code&gt;selectable&lt;/code&gt;: &lt;code&gt;isSelectable()&lt;/code&gt;, &lt;code&gt;assertIsSelected()&lt;/code&gt;, and &lt;code&gt;.assertIsNotSelected()&lt;/code&gt;. &lt;/p&gt;
&lt;h3 id=&quot;clickable&quot;&gt;Clickable&lt;/h3&gt;
&lt;p&gt;The final custom component for this blog post is a custom button that can be used to bookmark an item:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/26Mu60wqpzb0ghGHeF7ynS/4f320375013d34fdb4b539d042c09839/Screenshot_2023-07-10_at_8.57.48.png&quot; alt=&quot;A pink rectangle with rounded corners, which has the text &amp;quot;Bookmark this item&amp;quot; on the left side, and at the end (right side), there is an outlined icon representing a bookmark.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We want to ensure that it has a name (so, the text &amp;quot;Bookmark this item&amp;quot;), the role of a button, and a state that communicates whether the item is bookmarked or not. &lt;/p&gt;
&lt;p&gt;The following tests ensure that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class ClickableTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun hasRoleNameValue() {
        composeTestRule.setContent {
            ModifiersExampleTheme {
                ClickableScreen()
            }
        }

        val clickableElement = 
            composeTestRule.onNode(hasTestTag(&amp;quot;clickable&amp;quot;))

        // Assert accessible name
        clickableElement.assertTextEquals(&amp;quot;Bookmark this item&amp;quot;)

        // Assert role
        clickableElement.assert(
            SemanticsMatcher(&amp;quot;has correct role&amp;quot;) {
                it.config.getOrNull(SemanticsProperties.Role) == Role.Button
            },
        )

        // Assert state description
        clickableElement.assertStateDescription(&amp;quot;Not bookmarked&amp;quot;)
        clickableElement.performClick()
        clickableElement.assertStateDescription(&amp;quot;Bookmarked&amp;quot;)
    }

    private fun SemanticsNodeInteraction.assertStateDescription(
        stateDescription: String
    ) =
        assert(
            SemanticsMatcher(&amp;quot;has correct state description&amp;quot;) {
                it.config.getOrNull(SemanticsProperties.StateDescription) == stateDescription
            },
        )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, the setup and checking of the name are similar to the other two components. But to check if the component has a role of the button, we need to use a &lt;code&gt;SemanticsMatcher&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/ui/test/SemanticsMatcher&quot;&gt;SemanticsMatcher&lt;/a&gt; is a wrapper for matching semantic nodes. We want to ensure that the element&amp;#39;s semantic property &lt;code&gt;Role&lt;/code&gt; matches &lt;code&gt;Role.Button&lt;/code&gt;. We can do it by wrapping our check with a &lt;code&gt;SemanticMatcher&lt;/code&gt;, and getting the element&amp;#39;s &lt;code&gt;SemanticProperties.Role&lt;/code&gt; from the element with &lt;code&gt;it.config.getOrNull(SemanticsProperties.Role)&lt;/code&gt; and checking its value. &lt;/p&gt;
&lt;p&gt;The same pattern works for testing the element&amp;#39;s state description. To avoid code duplication, I&amp;#39;ve created an extension function, &lt;code&gt;assertStateDescription,&lt;/code&gt; which is used to check the state description of the element. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve discussed writing accessibility tests for the WCAG success criteria 4.1.2: Name, Role, Value. While they&amp;#39;re not always relevant to mobile accessibility, this blog post aims to give an example of how to write accessibility tests. &lt;/p&gt;
&lt;p&gt;Have you written tests for accessibility on Android? Please, share what you&amp;#39;ve learned!&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;Improving Android Accessibility with Modifiers in Jetpack Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html&quot;&gt;Name, Role, Value&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/ui/test/SemanticsMatcher&quot;&gt;SemanticsMatcher&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Pride in Your App  - Trying Out GraphQL on Android</title>
    <link href="https://eevis.codes/blog/2024-06-14/pride-in-your-app-trying-out-graphql-on-android/" />
    <updated>2024-06-14T02:56:23.859Z</updated>
    <id>https://eevis.codes/blog/2024-06-14/pride-in-your-app-trying-out-graphql-on-android/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/25EER3Ohw0rVFIGNPC3eIx/57f09e9f6494548d41873491f283c9f6/graphql-pride-square__1_.png"/>]]>
      &lt;p&gt;It&amp;#39;s Pride month, y&amp;#39;all! As someone who is part of the LGBTIQA+ community, this month is both great and stressful at the same time. You never know what some people come up with - somehow, this month draws some really nasty people out. And with the rise of far-right in Europe... I&amp;#39;m not even going to get started. &lt;/p&gt;
&lt;p&gt;I have wanted to try out GraphQL with Android for quite a while, and when I came across this &lt;a href=&quot;https://pride.dev/&quot;&gt;Pride Flag API&lt;/a&gt;, I thought that now was the time. What a great way to celebrate Pride by trying out the API (and GraphQL) and writing a blog post about it!&lt;/p&gt;
&lt;p&gt;A bit of background about me and GraphQL: I started my public speaking career talking about GraphQL (and JavaScript. That&amp;#39;s my dark secret.). It was fun, and I liked it a lot. Of course, there are always pros and cons when thinking about using any technology, but nevertheless, I liked it a lot. I even got this &amp;quot;Ask me about GraphQL&amp;quot; T-shirt from one meetup, and that was probably the coolest t-shirt I&amp;#39;ve gotten. &lt;/p&gt;
&lt;p&gt;When I switched to Android, there were no use cases for me to learn how to integrate GraphQL with Android. So, I&amp;#39;ve wanted to try it for a while now, and I&amp;#39;m glad I found a great way to test it. Let&amp;#39;s get started!&lt;/p&gt;
&lt;h2 id=&quot;graphql&quot;&gt;GraphQL&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;ve never heard the word GraphQL, it&amp;#39;s a query language and an alternative way of building APIs in the form of graphs - hence the name. &lt;/p&gt;
&lt;p&gt;GraphQL APIs let you ask for what you need - and only that. So, compared to REST APIs, which always return everything, GraphQL APIs return only the values you&amp;#39;ve asked for. And as it&amp;#39;s graph-based, you can get nested data with one query. An example would be authors, their books, and the characters in the books - with REST API, you&amp;#39;d need to make three requests (depending on the API, naturally). &lt;/p&gt;
&lt;p&gt;GraphQL offers three actions: querying, mutating, or subscribing to data. Compared to REST, querying matches the &lt;code&gt;GET&lt;/code&gt; requests, mutating matches all the others that mutate data, and subscribing is a two-way street - like, for example, WebSockets. &lt;/p&gt;
&lt;p&gt;In the context of this blog post, we&amp;#39;re going to &lt;em&gt;query&lt;/em&gt; data. To learn more about the operations available and GraphQL in general, head over to &lt;a href=&quot;https://graphql.org/learn/&quot;&gt;GraphQL&amp;#39;s documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To use GraphQL, we&amp;#39;re going to use &lt;a href=&quot;https://www.apollographql.com/docs/kotlin/&quot;&gt;Apollo Kotlin&lt;/a&gt; (formerly Apollo Android). The library is type-safe and compatible with Kotlin Multiplatform.&lt;/p&gt;
&lt;h2 id=&quot;dependencies-and-getting-the-schema&quot;&gt;Dependencies and Getting the Schema&lt;/h2&gt;
&lt;p&gt;To get started with Apollo Kotlin, we need to add some dependencies to the project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// project-level build.gradle.kts 

plugins {
  id(&amp;quot;com.apollographql.apollo3&amp;quot;).version(&amp;quot;3.8.4&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// module-level build.gradle.kts 

dependencies {
  implementation(&amp;quot;com.apollographql.apollo3:apollo-runtime:3.8.4&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the version numbers for these packages must be the same. &lt;/p&gt;
&lt;p&gt;In addition to dependencies, we need to add a package name addition task for the generated models:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// module-level build.gradle.kts 

apollo { 
    service(&amp;quot;service&amp;quot;) { 
        packageName.set(&amp;quot;com.example&amp;quot;) 
    } 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With many projects, syncing gradle files would happen right here. However, this project is different; we still need the schema for the GraphQL queries. There are several options, but with an external API, downloading the schema with introspection is the easiest. We need to save the schema to &lt;code&gt;src/main/graphql&lt;/code&gt;, and in the case of this small project, we do it with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;./gradlew :app:downloadApolloSchema --endpoint=&amp;#39;https://pride.dev/api/graphql&amp;#39; --schema=app/src/main/graphql/schema.graphqls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we have the schema, it&amp;#39;s time to sync the gradle files and continue to querying the data. &lt;/p&gt;
&lt;h2 id=&quot;querying-data&quot;&gt;Querying Data&lt;/h2&gt;
&lt;p&gt;For this small app, we want to read the data - so, in GraphQL&amp;#39;s terms, we want to query it. We don&amp;#39;t need every property for the flags, so we define a query with just what we need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-graphql&quot;&gt;query FlagQuery {
  allFlags {
    name
    year
    svgUrl
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With Apollo Kotlin, all queries need to be named queries. You&amp;#39;ll run into an error if you have an unnamed query - trust me, I know. &lt;/p&gt;
&lt;p&gt;This query gets all the available flags from the API. We need the flag&amp;#39;s name, year, and SVG URL, so we write those properties in our query. &lt;/p&gt;
&lt;p&gt;To add the query to the Android project, we first need to create a file in the &lt;code&gt;src/main/&lt;/code&gt; folder, which is the very same folder where the schema lives. Let&amp;#39;s call the file &lt;code&gt;FlagQuery.graphql&lt;/code&gt; and add the query there. Apollo generates the model for the query through an automated task that runs when the app is built. So, let&amp;#39;s build the app, and then query&amp;#39;s model will be available. &lt;/p&gt;
&lt;p&gt;At this point, I want to remind you to add the internet permission to &lt;code&gt;AndroidManifest&lt;/code&gt;. If you don&amp;#39;t, well, the following steps will become more complicated. Trust me, I know. &lt;/p&gt;
&lt;p&gt;Here&amp;#39;s a reminder on what to add, so you don&amp;#39;t need to use a search engine to find it, like I did:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;// AndroidManifest.xml

&amp;lt;uses-permission android:name=&amp;quot;android.permission.INTERNET&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, now we have the query, but how do we connect it to the UI? The answer is a GraphQL client, which we can use to query the data. I&amp;#39;m making things straightforward for the purposes of this demo, so we only define a GraphQL client and then query the data in a view model. In a production-grade app, you&amp;#39;d probably have a more layered architecture.&lt;/p&gt;
&lt;p&gt;First, we define the client:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// GraphQLClient.kt

const val API_ENDPOINT = &amp;quot;https://pride.dev/api/graphql&amp;quot;

val apolloClient = ApolloClient.Builder()
    .serverUrl(API_ENDPOINT)
    .build()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the view model, we first define a state to use in the UI:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// FlagsViewModel.kt

data class FlagsUiState(
    val loading: Boolean = true,
    val flags: List&amp;lt;FlagQuery.AllFlag&amp;gt; = emptyList(),
    val currentFlag: FlagQuery.AllFlag? = null,
)

class FlagsViewModel : ViewModel() {
    private var _state = MutableStateFlow(FlagsUiState())
    val state = _state.asStateFlow()
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want to store all the flags we get from the API and display the current flag on a detail page, so we have properties for &lt;code&gt;flags&lt;/code&gt; and &lt;code&gt;currentFlag&lt;/code&gt; in the state. The type (&lt;code&gt;FlagQuery.AllFlag&lt;/code&gt;) for both is generated by Apollo Kotlin. &lt;/p&gt;
&lt;p&gt;To query all the flags, let&amp;#39;s define a function inside the view model: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// FlagsViewModel.kt

suspend fun getFlags() {
    val flagsQuery = apolloClient
        .query(FlagQuery())
        .execute()

    _state.update { currentState -&amp;gt;
        currentState.copy(
            loading = false,
            flags = flagsQuery.data?.allFlags ?: emptyList(),
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, to get the flags, we need a suspend function, inside which we call the &lt;code&gt;apolloClient.query(...).execute()&lt;/code&gt; with the &lt;code&gt;FlagQuery&lt;/code&gt; we defined previously. On success, it returns data in the &lt;code&gt;data&lt;/code&gt; property, which we can then use to update the state with. &lt;/p&gt;
&lt;p&gt;I mentioned that we also want to store the current flag in the state. Setting it is a straightforward function without any GraphQL-magic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// FlagsViewModel.kt

fun setFlag(flag: FlagQuery.AllFlag) {
    _state.update {
        it.copy(
            currentFlag = flag,
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;a-couple-of-words-about-the-ui&quot;&gt;A Couple of Words About the UI&lt;/h2&gt;
&lt;p&gt;On the UI side, we&amp;#39;re doing pretty simple things: displaying a list of the flags and then navigating to a detail view. As it&amp;#39;s not related to GraphQL, meaning it&amp;#39;s just passing data as state, it&amp;#39;s out of the scope of this blog post. &lt;/p&gt;
&lt;p&gt;Here&amp;#39;s a video of how the experience looks like:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/5W91AkqqUwf0yF3ZTPvlxe/ef58576b6e971396b92be4dc6c040e88/flag-explorer.webm&quot; type=&quot;video/webm&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;If you&amp;#39;re interested in checking the UI code out, head over to the repository: &lt;a href=&quot;https://github.com/eevajonnapanula/flag-explorer/&quot;&gt;Flag Explorer&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we discussed GraphQL and how to use it in an Android project. As an example, we built a small app that fetches Pride flags from the &lt;a href=&quot;https://pride.dev/&quot;&gt;Pride Flag API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the end, adding GraphQL to the project was surprisingly straightforward. For some reason, I had thought it&amp;#39;d be much more complicated. As mentioned in the beginning, I have warm feelings towards GraphQL, so it&amp;#39;d be great to use it in some real projects. We&amp;#39;ll see what the future brings!&lt;/p&gt;
&lt;p&gt;What do you think of GraphQL? Have you tried it out? &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pride.dev/&quot;&gt;Pride Flag API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://graphql.org/learn/&quot;&gt;GraphQL&amp;#39;s documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.apollographql.com/docs/kotlin/&quot;&gt;Apollo Kotlin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/flag-explorer/&quot;&gt;Flag Explorer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>The Worst Case of Imposter Syndrome</title>
    <link href="https://eevis.codes/blog/2024-06-26/the-worst-case-of-imposter-syndrome/" />
    <updated>2024-06-26T03:06:30.434Z</updated>
    <id>https://eevis.codes/blog/2024-06-26/the-worst-case-of-imposter-syndrome/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3FSwvXrgzsCEaQP3J3KXcm/3babdd99fe97946d64963db255f48038/imposter-syndrome-square.png"/>]]>
      &lt;p&gt;I wrote most of this blog post in the spring, a bit after my burnout sickness leave. I&amp;#39;m still recovering, but I&amp;#39;m in a way better place. I found this draft a couple of days ago, and after reading it, I decided I&amp;#39;d finish it and publish it. Why? I think it speaks for itself. It contains the words I&amp;#39;d wanted to say back then but didn&amp;#39;t have the strength to say, even to finish the blog post and share it. &lt;/p&gt;
&lt;p&gt;And if you recognize yourself from the words, I see you. You are enough and have what it takes, even if it doesn&amp;#39;t feel like it. &lt;/p&gt;
&lt;h2 id=&quot;the-worst-case-of-imposter-syndrome&quot;&gt;The Worst Case of Imposter Syndrome&lt;/h2&gt;
&lt;p&gt;I think I&amp;#39;m experiencing the worst case of imposter syndrome in my career. It&amp;#39;s so bad that I&amp;#39;ve started considering switching careers because I can&amp;#39;t stand this deep feeling. And when I say switching careers, I&amp;#39;m not joking the way I usually joke when something mysterious happens with the code. No, this time, I&amp;#39;ve been considering leaving tech behind.&lt;/p&gt;
&lt;p&gt;It all started... Where it all started? I don&amp;#39;t even know. For a long time, I&amp;#39;ve been really confident. Not in an arrogant way, but in a way that I know what I know, and I definitely know what I don&amp;#39;t know. And sure, I&amp;#39;ve had setbacks, but I&amp;#39;ve always bounced back. &lt;/p&gt;
&lt;p&gt;Now it just feels like I&amp;#39;m going deeper and deeper into understanding how much I suck. And how little I know and how I shouldn&amp;#39;t even work in this position. How everyone knows that I&amp;#39;m a fraud. &lt;/p&gt;
&lt;p&gt;I can start dissecting this. And actually, that&amp;#39;s what I&amp;#39;ll do. I&amp;#39;ll tell you about some things that have led to this situation. It&amp;#39;s not because I blame someone (or actually, I do - myself), but I want to bring light to things that have caused my journey to end up here.&lt;/p&gt;
&lt;p&gt;Oh, and in the end, I won&amp;#39;t tell you how to get better. This is not one of those blog posts. I&amp;#39;m still on my way to getting out of the woods with all this. But I hope reading what has affected me might help you recognize some things on your journey that might lead to where I am and avoid them. This is most definitely not a fun place to be.&lt;/p&gt;
&lt;h2 id=&quot;burnout-leads-to-questioning-yourself&quot;&gt;Burnout Leads to Questioning Yourself&lt;/h2&gt;
&lt;p&gt;Burnout can cause low professional self-esteem, so it&amp;#39;s no wonder it can be one of the building blocks for imposter syndrome. It&amp;#39;s sneaky and builds over time, so the change is not always easy to spot. &lt;/p&gt;
&lt;p&gt;My advice: Don&amp;#39;t get burnt out. Do something before it&amp;#39;s too late. I know it&amp;#39;s not always easy to recognize that you&amp;#39;re on the path to burning out. Heck, I&amp;#39;ve been here many times, and I still don&amp;#39;t always recognize it well enough. &lt;/p&gt;
&lt;h2 id=&quot;the-words-of-others-can-do-a-lot-of-harm&quot;&gt;The Words of Others Can Do a Lot of Harm&lt;/h2&gt;
&lt;p&gt;To add to this burnout-related low self-esteem, hearing words like &amp;quot;this is what senior developers should know&amp;quot; and realizing you don&amp;#39;t know all that doesn&amp;#39;t help. I was part of a hiring committee for a senior Android developer role a while back, and at some point, I realized how others described what a senior Android developer should know. &lt;/p&gt;
&lt;p&gt;It was all technical details - trivial things I realized I didn&amp;#39;t know to describe on the spot. And the way they talked about the candidates and their skills, I just felt like I should get a demotion because I don&amp;#39;t have all the technical knowledge for a senior developer. Maybe not even for a mid-level developer.&lt;/p&gt;
&lt;p&gt;Hearing those conversations, I was ready to quit. I felt like I&amp;#39;m not good enough (despite being in a senior Android role for more than a year, so I had literally proven myself already). I felt like every single one of my Android colleagues must see me in a way that I don&amp;#39;t deserve to be a &lt;em&gt;senior&lt;/em&gt; developer, and that I was just lucky to get promoted.&lt;/p&gt;
&lt;p&gt;That feeling is paralyzing. You start to question your every decision and every line of code. You start waiting for something bad to happen, and every time there is a crash or bug that&amp;#39;s reported, you&amp;#39;re sure that it&amp;#39;s because of you (even if you haven&amp;#39;t merged any code for the past weeks because you were on a sickness leave). &lt;/p&gt;
&lt;p&gt;I&amp;#39;m getting better now, but this feeling and those thoughts are still nagging me. I can fight them, but I realize they affect the things I do a lot. Luckily, I have a colleague who keeps cheering me on and reminding me that I am skilled and good enough (thank you, Marianne ❤️). But I really hope that no one needs to go through those emotions I&amp;#39;ve gone through. So, let&amp;#39;s remember that our words affect people around us. &lt;/p&gt;
&lt;h2 id=&quot;constant-fighting-for-your-seat-at-the-table-does-not-help&quot;&gt;Constant Fighting for Your Seat at the Table Does Not Help&lt;/h2&gt;
&lt;p&gt;The other thing that contributes to this feeling is the constant fight to be heard and seen. I&amp;#39;ve been in countless meetings where I&amp;#39;ve been forgotten - like, let&amp;#39;s have a facilitated round of introductions, but let&amp;#39;s forget Eevis. At the time, I was so exhausted that when that happened, I was just silently crying and could not open my mic to say, hey, I&amp;#39;m here; please don&amp;#39;t forget me again.&lt;/p&gt;
&lt;p&gt;I often have felt like I&amp;#39;m speaking to walls. Or maybe something else, walls often don&amp;#39;t answer with &amp;quot;Oh that&amp;#39;s a good point&amp;quot; and then forget it all when it&amp;#39;s time to incorporate the feedback. &lt;/p&gt;
&lt;p&gt;I could also talk about the countless times when I&amp;#39;ve been the person who knows the answer to something because I&amp;#39;ve been the one working with the thing. And then, in some meeting, I&amp;#39;ll need to first wait for some man, who has just a vague idea of that thing, to explain what&amp;#39;s going on - and then start correcting them. Yes, I&amp;#39;m too nice to just cut there when they start. No, I won&amp;#39;t change that; it requires even more strength. I&amp;#39;ve been raised to be nice, polite, and not cut when others speak.&lt;/p&gt;
&lt;p&gt; I fully believe that none of these people do these things because they hate me or consciously think I&amp;#39;m lesser. But nevertheless, they keep repeating these actions, and it tires me out. I hate the constant fighting. I&amp;#39;d like to have a seat at the table because I&amp;#39;ve already earned it (because, trust me, I have). I hate playing this constant chair game where I need to prove myself over and over again, more than many of my colleagues do. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve described this all working twice as much as many others. And in fact, I do. So, it is no wonder I am burnt out and exhausted. And no wonder I have this feeling that even if I work twice as much, I&amp;#39;ll never be good enough as the others who don&amp;#39;t need to work that much. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;re my colleague reading this and recognize yourself from what I&amp;#39;ve written, I&amp;#39;m not blaming you. I fully believe you have good intentions in every instance. I&amp;#39;m just asking that you pay attention to the impact your actions might have — not just on me but on anyone around you.&lt;/p&gt;
&lt;p&gt;And if you&amp;#39;re experiencing similar things, I&amp;#39;d love to say things get better, but I have to be honest: I don&amp;#39;t know if they do. I know it&amp;#39;s possible to reach this state of mind where you&amp;#39;re confident and small things don&amp;#39;t affect you. I&amp;#39;ve been there. And I hope I&amp;#39;ll reach that state some day again. &lt;/p&gt;
&lt;p&gt;But before that, I hope I won&amp;#39;t end up switching careers. &lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Stacked Cards Layout With Compose  - And Cats</title>
    <link href="https://eevis.codes/blog/2024-07-11/stacked-cards-layout-with-compose-and-cats/" />
    <updated>2024-07-11T11:02:39.052Z</updated>
    <id>https://eevis.codes/blog/2024-07-11/stacked-cards-layout-with-compose-and-cats/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/ubsGgEVLj7F1UujcdmiQc/5e45d5372de5255d0ea99677d718523c/stacked-cards-square.png"/>]]>
      &lt;p&gt;I was writing a completely different blog post about playing around with Glance and app widgets, and I needed an example app. None of the existing ones served me, so I needed to build a new one. And I completely overdid it—I would have needed just something really simple, but I ended up creating a more polished app with some new concepts.&lt;/p&gt;
&lt;p&gt;The app I built has cats — a lot of cats— and you can get even more. It has cat pictures as cards. I wanted to stack the cards in a pile just because I thought I could probably do it — and I could! So this blog post is about building that stacked card layout — and a bit about cats.&lt;/p&gt;
&lt;p&gt;If you ever need cats, there&amp;#39;s this awesome API: &lt;a href=&quot;https://cataas.com/&quot;&gt;Cats as a service&lt;/a&gt;. I think it&amp;#39;s one of the most important ones out there. Just saying. &lt;/p&gt;
&lt;p&gt;Yes, I might end up being the cat lady in the future.&lt;/p&gt;
&lt;p&gt;But okay, back to the coding. In the next sections, I&amp;#39;ll first explain the stacked cards layout in more detail, then discuss custom layouts in Compose, and finally talk through the code for my version of the stacked cards layout.&lt;/p&gt;
&lt;h2 id=&quot;stacked-cards-with-cats&quot;&gt;Stacked Cards with Cats&lt;/h2&gt;
&lt;p&gt;The app&amp;#39;s idea is to fetch a cat picture, and then get more cat pictures once you&amp;#39;ve seen one. Nothing complex, just cats. The app fetches a random picture&amp;#39;s JSON data from Cataas-API and stores the picture&amp;#39;s ID in a data store. Why a data store instead of, for example, Room? Well, this is a simple example app, never intended for production use, and it needs really simple data—a set of strings. &lt;/p&gt;
&lt;p&gt;Further, it renders pictures with Coil&amp;#39;s &lt;code&gt;SubcomposeAsyncImage&lt;/code&gt; based on the URL, so it doesn&amp;#39;t load the images to the device. Again, it&amp;#39;s not a production app, and there is no need for offline support or similar things. For a more production-grade app, a lot of things should be improved. I&amp;#39;ve tried to make the code (a link at the end of the post!) as straightforward as possible, so I&amp;#39;ve cut some corners. &lt;/p&gt;
&lt;p&gt;So, if you&amp;#39;re someone recruiting Android devs, please don&amp;#39;t look at this code as my masterpiece and the proof of all I can do. Trust me, I write more robust code in production. I promise!&lt;/p&gt;
&lt;p&gt;The app UI shows the latest picture at the top of the screen and then the others stacked on the bottom of the screen. Users can remove the topmost picture from the stack by clicking the X-button that each card has in the top-right corner. &lt;/p&gt;
&lt;p&gt;Now that I&amp;#39;ve been just hinting about cat pictures and the stacked card layout, let&amp;#39;s finally see some pictures. Or a picture, to be more precise. Here&amp;#39;s what the stacked cards look like when there are a lot of cards in the pile:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/02EnfrSGwdgSt4juVUeUp/6c42a1372cab4c9d3a0e6f00215acca8/Screenshot_2024-07-09_at_16.13.24.png&quot; alt=&quot;Pile of cards stacked on each other. The topmost card has a close button with an X-icon on the top-left corner and a picture of a cat lying on the side and watching a bit under the camera. The cat has grey and white long fur.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;How did I accomplish this layout? I used the custom Layouts Compose provides. Let&amp;#39;s talk about that a bit next. &lt;/p&gt;
&lt;h2 id=&quot;custom-layouts&quot;&gt;Custom Layouts&lt;/h2&gt;
&lt;p&gt;Compose has some built-in layouts, such as &lt;code&gt;Column&lt;/code&gt;s and &lt;code&gt;Row&lt;/code&gt;s, but often custom layouts are needed. You can read more about custom layouts in Compose from the Android documentation: &lt;a href=&quot;https://developer.android.com/develop/ui/compose/layouts/custom&quot;&gt;Custom Layouts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are two ways of creating custom layouts with composable components: with a &lt;code&gt;layout&lt;/code&gt;- modifier or with a &lt;code&gt;Layout&lt;/code&gt; composable. The &lt;code&gt;layout&lt;/code&gt;- modifier modifies only the composable it&amp;#39;s called on, so it&amp;#39;s not an option in our case.&lt;/p&gt;
&lt;p&gt;As this layout is about more than one component, we want to build the layout with a &lt;code&gt;Layout&lt;/code&gt; composable. A &lt;code&gt;Layout&lt;/code&gt; composable allows us to measure and lay out multiple composables. The process for creating the layout consists of three phases (quote from the Custom Layouts documentation):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Each node must:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Measure any children&lt;/li&gt;
&lt;li&gt;Decide its own size&lt;/li&gt;
&lt;li&gt;Place its children&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;We&amp;#39;ll get to a concrete example of all these in a bit, but this is the process for most custom layouts out there. &lt;/p&gt;
&lt;h2 id=&quot;show-me-the-code&quot;&gt;Show Me the Code&lt;/h2&gt;
&lt;p&gt;Okay, so to create the stacked cards layout, we&amp;#39;ll need a custom component that takes &lt;code&gt;modifier&lt;/code&gt; and &lt;code&gt;content&lt;/code&gt; as parameters. Of course, this composable could take in other things as well, but for this example, just those two are needed. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardStack.kt

@Composable
fun CardStack(
    modifier: Modifier = Modifier,
    content: @Composable () -&amp;gt; Unit,
) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, inside the &lt;code&gt;CardStack&lt;/code&gt;-component, we&amp;#39;ll define a &lt;code&gt;Layout&lt;/code&gt;, which takes in the same &lt;code&gt;content&lt;/code&gt; and &lt;code&gt;modifier&lt;/code&gt;-parameters. The third parameter is a &lt;code&gt;MeasurePolicy&lt;/code&gt; lambda, responsible for all the action and layout creation. The lambda has two params: &lt;code&gt;measurables&lt;/code&gt;, which is a list of &lt;code&gt;Measurable&lt;/code&gt;s, which all correspond to a layout child element. The other parameter is &lt;code&gt;constraints&lt;/code&gt;, the constraints for the layout. The following code shows all this more concretely: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardStack.kt

Layout(
    content,
    modifier,
) { measurables, constraints -&amp;gt;
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the &lt;code&gt;Layout&lt;/code&gt;-composable, we&amp;#39;ll need to do the three steps mentioned in the previous section. We first measure the children, then decide the size, and then place the children. &lt;/p&gt;
&lt;p&gt;The first step is to measure the children. For that, we&amp;#39;ll use the &lt;code&gt;measurables&lt;/code&gt;-parameter, map through it, and for each &lt;code&gt;Measurable&lt;/code&gt; item, call the &lt;code&gt;measure&lt;/code&gt;-function. We then store all of these into a variable called &lt;code&gt;placeable&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardStack.kt

val placeables =
    measurables.map { measurable -&amp;gt;
        measurable.measure(constraints)
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next step is to decide the size. As the cards are stacked on top of each other, the screen estate we need for the cards is pretty much the size of one card with some extra padding. Of course, we don&amp;#39;t want the cards to be exactly on top of each other to have the effect of piled cards, so the amount of extra padding needs to depend on how many cards there are. &lt;/p&gt;
&lt;p&gt;For the height of the layout, we want to check the height of one card, so we take the first card on the measured children variable (&lt;code&gt;placeables&lt;/code&gt;) and its height. In this example, the size of the cards is always the same, so setting the height is straightforward. For cards with differing sizes, some more calculations for the height would be needed.&lt;/p&gt;
&lt;p&gt;Then, we add some extra padding (in this case, 10 pixels) and multiply it by the children&amp;#39;s size. As the layout can take zero or more children, we want to account for the case when there are no children - so if the &lt;code&gt;placeables&lt;/code&gt; list is empty, we set the height to 0. &lt;/p&gt;
&lt;p&gt;For the layout&amp;#39;s width, we use the width of the first child if there are children and 0 if there are none. Then, we define &lt;code&gt;layout&lt;/code&gt; with these height and width. This all looks the following in code: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardStack.kt

val height = if (placeables.isNotEmpty())
        placeables.first().height +
            (CardStack.EXTRA_PADDING * placeables.size)
    else 0

val width = if (placeables.isNotEmpty())
        placeables.first().width
    else 0

layout(width = width, height = height) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final step is to place the children. That happens inside the &lt;code&gt;layout&lt;/code&gt; we defined. We want to map through the children (&lt;code&gt;placeables&lt;/code&gt;) and then call the &lt;code&gt;place&lt;/code&gt;-method. It takes in x and y coordinates, and we want to use those coordinates for a bit of misalignment to create a more realistic look. &lt;/p&gt;
&lt;p&gt;So, for the x-value, we either place it at coordinate 0 in the parent&amp;#39;s coordinate system or put it to x-position of 5 (defined outside the code snippet). The decision depends on if the value is odd or even - if it&amp;#39;s even, then we use 0. Otherwise, we use 5. The &lt;code&gt;isEven&lt;/code&gt;-function is an extension function I&amp;#39;ve defined to reduce repetition for checking if an integer is even. &lt;/p&gt;
&lt;p&gt;For the y-position, we want to multiply the y-position (5, defined outside the code snippet, see the full code below) with the index of the current element to create the stacked effect showing cards underneath the topmost card. This all translates to code the following way:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardStack.kt

layout(width = width, height = height) {
    placeables.mapIndexed { index, placeable -&amp;gt;
        placeable.place(
            x = if (index.isEven())
                0
            else
                CardStack.X_POSITION,
            y = CardStack.Y_POSITION * index,
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final code for the custom layout looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CardStack.kt

@Composable
fun CardStack(
    modifier: Modifier = Modifier,
    content: @Composable () -&amp;gt; Unit,
) {
    Layout(
        content,
        modifier,
    ) { measurables, constraints -&amp;gt;

        val placeables =
            measurables.map { measurable -&amp;gt;
                measurable.measure(constraints)
            }

        val height = if (placeables.isNotEmpty())
                placeables.first().height +
                    (CardStack.EXTRA_PADDING * placeables.size)
            else 0

        val width = if (placeables.isNotEmpty())
                placeables.first().width
            else 0

        layout(width = width, height = height) {
            placeables.mapIndexed { index, placeable -&amp;gt;
                placeable.place(
                    x = if (index.isEven())
                        0
                    else
                        CardStack.X_POSITION,
                    y = CardStack.Y_POSITION * index,
                )
            }
        }
    }
}

object CardStack {
    const val EXTRA_PADDING = 10
    const val Y_POSITION = 5
    const val X_POSITION = 5
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But we still need to do one thing to accomplish the UI we saw. Right now, the stacked layout looks like this: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/7dt0qCZUiP28VYv6j24X3j/2f164717983d9ffe7f486efe4253debb/Screenshot_2024-07-10_at_8.34.15.png&quot; alt=&quot;Cards are stacked in a way that they align horizontally almost perfectly. The topmost card has a picture of a cat sleeping in a basket, with the front paws cutely on the edge of the basket. &quot; /&gt;&lt;/p&gt;
&lt;p&gt;The cards are laid out on top of each other, aligning pretty well. However, we want a bit of randomness and rotation, like real, physical cards when they&amp;#39;re piled. We&amp;#39;ll add this effect to the cards themselves with a modifier.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;MainScreen&lt;/code&gt;, we map through cat picture ids and then show the &lt;code&gt;CatCard&lt;/code&gt; component with the id. This is where we add the &lt;code&gt;rotate&lt;/code&gt; modifier with a random amount of degrees for rotation.&lt;/p&gt;
&lt;p&gt;Inside the mapping, we define the &lt;code&gt;degrees&lt;/code&gt;- variable, remember it between recompositions, and then pass that value to the &lt;code&gt;rotate&lt;/code&gt;-modifier. For the value of degrees, we want to have a number within a range from -2 to 2. As &lt;code&gt;Random.nextFloat()&lt;/code&gt; doesn&amp;#39;t allow us define the range, we&amp;#39;ll use &lt;code&gt;Random.nextInt()&lt;/code&gt; and then convert the value to a float. &lt;/p&gt;
&lt;p&gt;You might ask now why we need to remember the variable. Otherwise, this random number would be regenerated on every recomposition (when an item is either added or removed from the list), causing the cards to change their positions on every recomposition. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

CardStack {
    catIds.value.mapIndexed { index, id -&amp;gt;
        val degrees = remember {
            Random.nextInt(-2, 2).toFloat()
        }

        AnimatedCatCard(
            modifier = Modifier.rotate(degrees),
            ...
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After these changes, we have a stacked card layout, shown in the Stacked Cards with Cats section. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked into custom layouts with Compose by building a stacked cards layout for cat photos. We accomplished this with the &lt;code&gt;Layout&lt;/code&gt;-composable, and some calculations. To finalize the layout, we added a bit of random rotation with a &lt;code&gt;rotate&lt;/code&gt;-modifier. &lt;/p&gt;
&lt;p&gt;The full code for this app can be found in the &lt;a href=&quot;https://github.com/eevajonnapanula/cats/&quot;&gt;Cats-repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Have you built custom layouts? Anything fun to share? Or any learnings?&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cataas.com/&quot;&gt;Cats as a service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/develop/ui/compose/layouts/custom&quot;&gt;Custom Layouts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/cats/&quot;&gt;Cats-repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Don&#39;t Lock the Screen Orientation! Handling Orientation in Compose</title>
    <link href="https://eevis.codes/blog/2024-07-18/dont-lock-the-screen-orientation-handling-orientation-in-compose/" />
    <updated>2024-07-18T03:57:44.737Z</updated>
    <id>https://eevis.codes/blog/2024-07-18/dont-lock-the-screen-orientation-handling-orientation-in-compose/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/5E4FOThfFDrk0xqxDjHMfu/6ae3ede0aef0bc2133e8eff4dca4906f/screen-orientation-square.png"/>]]>
      &lt;p&gt;Yes, your app should work in both portrait and landscape modes - unless locked orientation is essential for functionality, like in a piano app. However, for most apps, locked orientation is not an option. Oh, and yes, the functionality should be similar in both modes, not hiding anything when the orientation changes.&lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;ll cover the topic of locked orientation. It&amp;#39;s mainly concentrated on Compose things, but there might be something useful for Views, too. I&amp;#39;ve also included some good articles in the &lt;a href=&quot;https://eevis.codes/blog/2024-07-18/dont-lock-the-screen-orientation-handling-orientation-in-compose/#read-more-and-links-in-the-blog-post&quot;&gt;Read More&lt;/a&gt; section of the blog post, explaining, e.g., more about orientation locking themes on Views. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;This blogpost was updated on 22nd of July to add example of using &lt;code&gt;WindowWidthSizeClass&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-locking-orientation-is-a-problem&quot;&gt;Why Locking Orientation is a Problem?&lt;/h2&gt;
&lt;p&gt;When discussing locked orientation and its problems, we usually refer to locking the orientation to portrait mode. Unfortunately, this is a typical case among Android apps. &lt;/p&gt;
&lt;p&gt;Why is this a problem, then? Some people prefer to use their phones in landscape mode because it gives them more screen width. A wider screen can make it easier for people who use, e.g., larger font sizes of screen magnification. And some people might have their phones mounted on, e.g., a wheelchair in landscape mode, and can&amp;#39;t rotate the phone at all. &lt;/p&gt;
&lt;p&gt;In these cases, if the app&amp;#39;s orientation is locked in portrait mode, using the app might be more complicated or even impossible. That is not what any developer behind the apps wants, right? So, let&amp;#39;s talk about how to support different orientations on your app. &lt;/p&gt;
&lt;h2 id=&quot;its-straightforward&quot;&gt;It&amp;#39;s Straightforward&lt;/h2&gt;
&lt;p&gt;From a purely UI point of view, supporting landscape mode is pretty straightforward. You just don&amp;#39;t do anything - like prevent the screen rotation. This way, the landscape mode is available.&lt;/p&gt;
&lt;p&gt;Of course, you need to test that the layouts actually look okay in landscape orientation. With Compose, the layout mostly works if you don&amp;#39;t use exact widths for different elements and don&amp;#39;t have sticky items that take up some vertical space on the screen. However, in case you notice some problems, I&amp;#39;ve described one way to handle different layouts for landscape and portrait modes later in this post.&lt;/p&gt;
&lt;h2 id=&quot;but-you-need-to-remember-configuration-changes&quot;&gt;But You Need to Remember Configuration Changes&lt;/h2&gt;
&lt;p&gt;But there is one thing: You need to remember configuration changes. Switching the screen orientation is a configuration change, which can then trigger all kinds of things. &lt;/p&gt;
&lt;p&gt;Android developer documentation has an extensive page on reacting to configuration changes and the logic behind them: &lt;a href=&quot;https://developer.android.com/guide/topics/resources/runtime-changes&quot;&gt;Handle Configuration Changes&lt;/a&gt;. In the following subsections, I&amp;#39;m going to concentrate on Jetpack Compose-related things. We&amp;#39;ll first discuss UI-related changes and then data-related changes. &lt;/p&gt;
&lt;h3 id=&quot;handling-ui-changes&quot;&gt;Handling UI Changes&lt;/h3&gt;
&lt;p&gt;Although I mentioned that the UI usually looks pretty okay in landscape mode if there are no sticky elements or fixed widths for elements, the experience could often be better.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s say we have a button with a short text like &amp;quot;Save&amp;quot; that takes the full width in portrait mode. In landscape mode, we would like to keep the button&amp;#39;s width at 75% of the screen width. We have two ways to do this: Orientation and &lt;code&gt;WindowWidthSizeClass&lt;/code&gt;. I&amp;#39;ll first give an example with orientation, then discuss &lt;code&gt;WindowWidthSizeClass&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;orientation&quot;&gt;Orientation&lt;/h4&gt;
&lt;p&gt;One way to accomplish this is to get the current orientation and then act on its value. We can get it from &lt;code&gt;LocalConfiguration&lt;/code&gt; composition local. Let&amp;#39;s look at an example: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun OrientationExample() { 
    val orientation = LocalConfiguration.current.orientation
    when (orientation) {
        Configuration.ORIENTATION_LANDSCAPE -&amp;gt; {
            // Version for Landscape orientation
        }
        else -&amp;gt; {
            // Version for Portrait orientation
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we first read the orientation from &lt;code&gt;LocalConfiguration.current&lt;/code&gt; and then do something based on its value. So, for the example of our button, we could use this to save the value modifier uses for the width:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val buttonWidth = when (orientation) {
    Configuration.ORIENTATION_LANDSCAPE -&amp;gt; 0.75f
    else -&amp;gt; 1f
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then use it in the code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Button(
    modifier = Modifier.fillMaxWidth(buttonWidth)
    ...
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, our UI can react to screen orientation changes to provide a better user experience. &lt;/p&gt;
&lt;h4 id=&quot;windowwidthsizeclass&quot;&gt;&lt;code&gt;WindowWidthSizeClass&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;There&amp;#39;s another, a bit more flexible way to handle different screen widths as well: &lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/material3/windowsizeclass/WindowWidthSizeClass&quot;&gt;&lt;code&gt;WindowWidthSizeClass&lt;/code&gt;&lt;/a&gt;. It&amp;#39;s a class that represents breakpoints and helps to build flexible layouts. As the documentation describes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt; Each window size class breakpoint represents a majority case for typical device scenarios so your layouts will work well on most devices and configurations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://appt.org/en/docs/jetpack-compose/samples/screen-orientation&quot;&gt;Appt.org gives an example of handling screen orientation in Jetpack Compose&lt;/a&gt; with &lt;code&gt;WindowWidthSizeClass&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// Appt.org&amp;#39;s Jetpack Compose code example

@Composable
fun WindowSizeExample(widthSizeClass: WindowWidthSizeClass) {
    when(widthSizeClass) {
        WindowWidthSizeClass.Expanded -&amp;gt; 
            // orientation is landscape in most devices including foldables (width 840dp+)
        WindowWidthSizeClass.Medium -&amp;gt; 
            // Most tablets are in landscape, larger unfolded inner displays in portrait (width 600dp+)
        WindowWidthSizeClass.Compact -&amp;gt; 
            // Most phones in portrait
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, the UI would be even more flexible, handling landscape and portrait modes and different screen configurations, such as foldable phones.  &lt;/p&gt;
&lt;h3 id=&quot;handling-data-with-configuration-changes&quot;&gt;Handling Data with Configuration Changes&lt;/h3&gt;
&lt;p&gt;The other issue with non-locked screen orientation is data loss. If we store something on a composable, it will be recreated on each recomposition unless we wrap it to &lt;code&gt;remember&lt;/code&gt;, which saves things over recompositions. &lt;code&gt;remember&lt;/code&gt;, however, doesn&amp;#39;t store its value through activity recreation, which happens with configuration changes. &lt;/p&gt;
&lt;p&gt;If you need to store something inside a composable through configuration changes, you can use &lt;code&gt;rememberSaveable&lt;/code&gt;. It works well with primitive data types, but if you need to store some more complex data, you&amp;#39;ll need to utilize different state-saving methods listed in the Android documentation: &lt;a href=&quot;https://developer.android.com/develop/ui/compose/state#ways-to-store&quot;&gt;State and Jetpack Compose - Ways to store state&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There&amp;#39;s one more thing I want to talk about: Business logic. If you&amp;#39;re storing your data to a &lt;code&gt;ViewModel&lt;/code&gt;, orientation changes are handled on that side automatically. However, note that &lt;code&gt;ViewModel&lt;/code&gt;s don&amp;#39;t survive system-initiated process deaths. For that, Android documentation suggests using &lt;code&gt;SavedState&lt;/code&gt; APIs, and if you want to read more about that, the information is in &lt;a href=&quot;https://developer.android.com/develop/ui/compose/state-saving#savedstatehandle_apis&quot;&gt;Save UI state in Compose - &lt;code&gt;SavedStateHandle&lt;/code&gt; APIs&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve discussed screen orientation changes, how a non-locked screen orientation is an accessibility requirement, and what aspects you should consider when developing applications related to screen orientation. &lt;/p&gt;
&lt;p&gt;What&amp;#39;s your experience with locked screen orientations, either as a user or as a developer?&lt;/p&gt;
&lt;h2 id=&quot;read-more-and-links-in-the-blog-post&quot;&gt;Read More And Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/mesmerhq/do-you-have-to-support-landscape-in-your-android-app-812ac4ca8726&quot;&gt;Doug Stevenson - Do you have to support landscape in your Android app?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mobilea11y.com/quick-wins/landscape/&quot;&gt;Mobile A11y - Quick Win - Support Landscape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/guide/topics/resources/runtime-changes&quot;&gt;Handle Configuration Changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/material3/windowsizeclass/WindowWidthSizeClass&quot;&gt;&lt;code&gt;WindowWidthSizeClass&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://appt.org/en/docs/jetpack-compose/samples/screen-orientation&quot;&gt;Appt.org gives an example of handling screen orientation in Jetpack Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/develop/ui/compose/state#ways-to-store&quot;&gt;State and Jetpack Compose - Ways to store state&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/develop/ui/compose/state-saving#savedstatehandle_apis&quot;&gt;Save UI state in Compose - &lt;code&gt;SavedStateHandle&lt;/code&gt; APIs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Accessibility Considerations with Stacked Cards Custom Layout</title>
    <link href="https://eevis.codes/blog/2024-07-25/accessibility-considerations-with-stacked-cards-custom-layout/" />
    <updated>2024-07-25T03:26:41.374Z</updated>
    <id>https://eevis.codes/blog/2024-07-25/accessibility-considerations-with-stacked-cards-custom-layout/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/5Q3f8FFz3oy1L4wC2YWCM9/08e19d5c9c0a78386b926e86e458b95f/stacked-cards-a11y-square.png"/>]]>
      &lt;p&gt;A couple of weeks ago, I published a blog post &lt;a href=&quot;https://eevis.codes/blog/2024-07-11/stacked-cards-layout-with-compose-and-cats/&quot;&gt;Stacked Cards Layout With Compose - And Cats&lt;/a&gt;. The layout has some accessibility issues, and I aim to fix some of them in this blog post. There are a couple of issues I&amp;#39;m not fixing here because of the scope of this blog post; instead, I&amp;#39;m discussing the problems they&amp;#39;re causing and possible solutions. &lt;/p&gt;
&lt;p&gt;This time, I will also be pointing out some things that are good about the layout. I feel like I&amp;#39;m often just talking about problems and trying to find them, but this time, I&amp;#39;ll share something that&amp;#39;s working well, too. &lt;/p&gt;
&lt;p&gt;Before we dive into the different aspects I chose for this blog post, here is a fair warning: This list is not extensive and doesn&amp;#39;t contain all possible accessibility problems that this kind of layout might have. I didn&amp;#39;t test with every possible assistive technology or setting. My goal was progress over perfection, and I believe that the modifications I&amp;#39;ll list are already significant improvements to the accessibility of this layout.&lt;/p&gt;
&lt;h2 id=&quot;switch-access&quot;&gt;Switch Access&lt;/h2&gt;
&lt;p&gt;The first assistive technology I&amp;#39;m going to talk about and improve the app for is Switch Access. It&amp;#39;s a service that lets the user navigate their phone with one or more switches. If you want to learn more, the Android Accessibility Checklist I&amp;#39;ve created has a section about Switch Access: &lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/switch-access&quot;&gt;Test with Switch Access&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When testing with Switch Access, there was one problem I could find: When removing a card from the stack, the focus didn&amp;#39;t go anywhere. It just disappeared. The reason for that is that the component on which the focus was had been deleted, and the accessibility service lost the focus. &lt;/p&gt;
&lt;p&gt;One way to fix this issue is by moving the click event to the stack-component instead of the card itself. We can do this by adding a &lt;code&gt;semantics&lt;/code&gt;-modifier with an &lt;code&gt;onClick&lt;/code&gt;-lambda to the &lt;code&gt;CardStack&lt;/code&gt;&amp;#39;s &lt;code&gt;modifier&lt;/code&gt;-property. Let&amp;#39;s also move the &lt;code&gt;lastItem&lt;/code&gt; outside the &lt;code&gt;CardStack&lt;/code&gt; so that we can use it in the function we add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

val lastItem = catIds.value.last()

CardStack(
    Modifier.semantics {
        onClick {
            deleteCat(lastItem)
            true
        }
    },
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The&lt;code&gt; onClick&lt;/code&gt; function returns a boolean value because, under the hood, it&amp;#39;s an &lt;code&gt;AccessibilityAction&lt;/code&gt;. Accessibility actions return a boolean indicating whether the action was successfully handled. &lt;/p&gt;
&lt;p&gt;This solution is not perfect - but it&amp;#39;s progress. This way, the focus stays in place, and the user doesn&amp;#39;t need to navigate back to the stack to be able to delete the next card.&lt;/p&gt;
&lt;h2 id=&quot;voice-access&quot;&gt;Voice Access&lt;/h2&gt;
&lt;p&gt;One thing that could be improved for Voice Access usage is enhancing the content labels for the delete buttons. Even if we disable the buttons under the topmost card, there are two delete buttons: the topmost card and the single card at the top of the screen. &lt;/p&gt;
&lt;p&gt;Why is this a problem? When a Voice Access user says &amp;quot;Tap Delete,&amp;quot; there are two options for deleting. How Voice Access works is that when there is more than one available action with the same label, it displays numbers for each action, and then the user can select from them. So, in this case, to delete the card from the stack, the user would need to say something like &amp;quot;Tap Delete. Two.&amp;quot; &lt;/p&gt;
&lt;p&gt;That all works and doesn&amp;#39;t sound like much of a problem, but if you&amp;#39;re navigating with your voice all day, any way to save using it would be preferable. To be honest, I&amp;#39;m not sure if there is a good way to solve this issue for this particular case and if it&amp;#39;s a priority to solve it (compared to some more obvious cases), but this is good to keep in mind. &lt;/p&gt;
&lt;h2 id=&quot;screen-reader&quot;&gt;Screen Reader&lt;/h2&gt;
&lt;p&gt;There are a couple of problems for screen reader users. The most clear one is that this application displays images, and no content descriptions are available for any of the pictures. As the app uses images from an API, the responsibility of writing the content descriptions would fall under the API developers, and the app should just be able to use the descriptions.&lt;/p&gt;
&lt;p&gt;Of course, if we wanted to improve the experience, we could think about some AI solutions to write text alternatives for the images. This solution could be an improvement, especially if the text is more descriptive than &amp;quot;Can be an image of an animal,&amp;quot; as Facebook did (does? I&amp;#39;m not on FB anymore) automated captions. However, there are always risks, too - there is no human control over the generated texts, and they could end up being anything. That&amp;#39;s why I&amp;#39;m advocating for the responsibility of the ones providing the images to write the text alternatives for them.&lt;/p&gt;
&lt;p&gt;Another issue for screen reader users is related to the same thing as with Voice Access users: Delete buttons and their labels. Right now, especially without the content descriptions, screen reader users just have two delete buttons, with no context on what the heck they delete. To be honest, this UI right now (without the content descriptions) really sucks for anyone who can&amp;#39;t see and relies on a screen reader for navigation. &lt;/p&gt;
&lt;p&gt;Another problem I discovered was the same as for Switch Access: The focus disappeared after pressing the Delete button. Luckily, the same code also fixes the issue for a screen reader user, so we can mark that issue resolved.&lt;/p&gt;
&lt;p&gt;The last improvement for screen reader users we&amp;#39;re going to do is that we&amp;#39;ll add semantic information for headings to help with navigation. We&amp;#39;ll do this by adding a semantics modifier to the &lt;code&gt;Title&lt;/code&gt;-component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt 

@Composable
fun Title(text: String) {
    Text(
        modifier = Modifier.semantics { heading() }, 
        text = text,
        style = MaterialTheme.typography.titleLarge,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, the title is annotated as a heading.&lt;/p&gt;
&lt;h2 id=&quot;keyboard&quot;&gt;Keyboard&lt;/h2&gt;
&lt;p&gt;The following accessibility consideration relates to keyboard navigation. When testing the app with a keyboard, I found two issues: First, all of the cards&amp;#39; delete buttons in the stack were focusable, and when a card was deleted, the focus was lost. &lt;/p&gt;
&lt;p&gt;A note before continuing: If you&amp;#39;re wondering why I&amp;#39;m mentioning the focus issue again, the reason is that focus works differently for keyboards and accessibility services like screen readers and switch devices. That&amp;#39;s why the problem needs to be handled twice. &lt;/p&gt;
&lt;h3 id=&quot;focus-only-on-visible-buttons&quot;&gt;Focus Only on Visible Buttons&lt;/h3&gt;
&lt;p&gt;First, we should make only the topmost card&amp;#39;s delete button focusable and remove the focusability from the others in the stack. We will need a couple of things for this: First, the card should know if it&amp;#39;s the topmost card, and we should set the &lt;code&gt;focusProperties&lt;/code&gt; based on that. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s pass the last id on stack to &lt;code&gt;CatCard&lt;/code&gt;, check if the current card is the last one, and set the focusability based on that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// CatCard.kt

@Composable
fun CatCard(
    ...
    lastIdOnStack: String,
    ...
) {
    val isLastOnStackPerIds =
        remember(id, lastIdOnStack) {
            id == lastIdOnStack
        }

ElevatedCard(...) {
    IconButton(
        modifier =
            Modifier
                .focusProperties {
                    canFocus = isLastOnStackPerIds
                },
    ...
    ) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, we remove the focusability of the delete buttons under the topmost card, as they&amp;#39;re not focusable if the id is not the same as the last card. &lt;/p&gt;
&lt;h3 id=&quot;recovering-focus-after-delete&quot;&gt;Recovering Focus After Delete&lt;/h3&gt;
&lt;p&gt;The second thing we&amp;#39;ll need to do is fix the disappearing focus after deleting a card. The solution sounds straightforward: We&amp;#39;ll need to make the previous card&amp;#39;s delete button focusable and move the focus to it. In code, this requires a bit more work. &lt;/p&gt;
&lt;p&gt;We&amp;#39;ll need a function to handle the deletion of the card. We&amp;#39;ll need to wait a bit after calling the ViewModel&amp;#39;s delete function to allow the second item in the stack to be focusable. After that, we&amp;#39;ll need to use &lt;code&gt;FocusManager&lt;/code&gt; to move focus to the previous item. In code, this would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

val scope = rememberCoroutineScope()
val focusManager = LocalFocusManager.current

fun deleteCat(id: String) {
    scope.launch {
        viewModel.deleteCat(id)
        delay(1000)
        focusManager.moveFocus(FocusDirection.Previous)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&amp;#39;ll need to switch to using this function instead of calling the &lt;code&gt;viewModel&lt;/code&gt;&amp;#39;s function we did before:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainScreen.kt

CardStack {
    ...
    AnimatedCatCard(...) {
        deleteCat(id = id)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After these changes, the focus goes to the previous item after card deletion.&lt;/p&gt;
&lt;h2 id=&quot;cognitive-considerations&quot;&gt;Cognitive Considerations&lt;/h2&gt;
&lt;p&gt;While the app is simple, it does have some cognitive accessibility considerations. One thing that I noticed is that the delete button only has an icon — not any text accompanying it. Not everyone recognizes or remembers that the X-icon means deleting (or closing) something. They might feel confused when seeing that and not know what to do with it or how to remove an item from the card stack. &lt;/p&gt;
&lt;p&gt;We could improve this by introducing accessibility settings for the app and adding a toggle to show the labels with icons. I&amp;#39;ve written a blog post about how to do that: &lt;a href=&quot;https://eevis.codes/blog/2024-04-21/toggle-labels-with-icons-personalizing-accessibility/&quot;&gt;Toggle Labels With Icons - Personalizing Accessibility&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;screen-orientation&quot;&gt;Screen Orientation&lt;/h2&gt;
&lt;p&gt;I also tested the app with landscape mode, an important accessibility feature. As I did not lock the orientation to portrait mode, and as the data is stored in data store instead of state within a composable, everything works well, as you can see in the video: &lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/48la3JIdpCpkoYXWpMnRi6/7abcb1dc809ca1928717e080e310245a/Screen_recording_20240715_070519.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;If you want to learn more about supporting both landscape and portrait modes for your app, I&amp;#39;ve written a blog post: &lt;a href=&quot;https://eevis.codes/blog/2024-07-18/dont-lock-the-screen-orientation-handling-orientation-in-compose/&quot;&gt;Don&amp;#39;t Lock the Screen Orientation! Handling Orientation in Compose&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;larger-font-and-increased-display-sizes&quot;&gt;Larger Font And Increased Display Sizes&lt;/h2&gt;
&lt;p&gt;As the app doesn&amp;#39;t have much text, increasing the font size works rather well, and the text scales. Also, the layout is flexible in size, so it works with increased display sizes. Here&amp;#39;s a screenshot from when the font size is on the biggest one and the display size is on the largest selection:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1AjFVPk6mG4CaB49akJyiF/f5604d0327fca1b48cc5486227ccd40d/Screenshot_20240718_161729.png&quot; alt=&quot;App screen with a button &#39;Get another cat&#39; at top, under it text &#39;All the other cats&#39; and then the stack of cards. Topmost card has a cat laying on its back a bed front paws stretched. The image is upside down.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked into ways to improve accessibility of a stacked cards layout. We&amp;#39;ve discussed Switch Access, keyboard, Voice Access, and screen reader accessibility, as well as screen orientation, larger font sizes and increased display sizes, and cognitive considerations. &lt;/p&gt;
&lt;p&gt;This &lt;a href=&quot;https://github.com/eevajonnapanula/cats/commit/18e4d7628c5e76aa685f19239ff9af18d52d957f&quot;&gt;commit&lt;/a&gt; contains all the changes mentioned in this blog post. It also contains some other minor changes, as the app has a bit more than just the stacked cards, so I had to modify other aspects to work with these changes as well. &lt;/p&gt;
&lt;p&gt;Did you learn something from this blog post? Have you improved the accessibility of custom layouts in some ways? Please share!&lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-07-11/stacked-cards-layout-with-compose-and-cats/&quot;&gt;Stacked Cards Layout With Compose - And Cats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/switch-access&quot;&gt;Test with Switch Access&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-04-21/toggle-labels-with-icons-personalizing-accessibility/&quot;&gt;Toggle Labels With Icons - Personalizing Accessibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-07-18/dont-lock-the-screen-orientation-handling-orientation-in-compose/&quot;&gt;Don&amp;#39;t Lock the Screen Orientation! Handling Orientation in Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/cats/commit/18e4d7628c5e76aa685f19239ff9af18d52d957f&quot;&gt;Commit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Android Accessibility Checklist</title>
    <link href="https://eevis.codes/blog/2024-08-07/android-accessibility-checklist/" />
    <updated>2024-08-07T02:56:34.666Z</updated>
    <id>https://eevis.codes/blog/2024-08-07/android-accessibility-checklist/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/4dSYTJjZUDxP5GgBXkS6IT/3bbfcbdaae59e0a2d802ceaf789278d9/android-a11y-checklist-square.png"/>]]>
      &lt;p&gt;I&amp;#39;m studying for my second master&amp;#39;s degree (for fun) and finished with my thesis in the spring. As part of my thesis, I developed an accessibility checklist for Android developers. While completing all the checks doesn&amp;#39;t guarantee that your app is 100% accessible, the checklist aims to help catch many possible accessibility problems. &lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;ll first share a bit about the development process and then share the checklist. If you&amp;#39;re curious, the whole checklist with accompanying material is behind the following link: &lt;a href=&quot;https://android-a11y-checks.netlify.app/&quot;&gt;Android Accessibility Checklist&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;As mentioned, developing the checklist was part of my Master&amp;#39;s thesis, which has been published, and you can find it from the following link: &lt;a href=&quot;https://osuva.uwasa.fi/handle/10024/17254&quot;&gt;Towards More Accessible Android Applications: An Actionable Accessibility Checklist for Android Developers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I conducted the research with the help of Design Science Research (DSR). I collected material for the initial checklist from prior research and available Android accessibility guidelines, evaluated it with five interviews with Android developers, and then constructed an improved checklist. Then, I created the website with the improved checklist and collected feedback and material for the last analysis with a Google Form. I analyzed the answers from both interviews and the questionnaire with the help of content analysis. The result of the research is the Android Accessibility Checklist, or as DSR calls it, an artifact.&lt;/p&gt;
&lt;p&gt;The checklist itself consists of two sections. The first contains the checks that every Android developer should do, and the findings should be fixable by the developer. The second section includes checks that might not be fixable by the developer alone and might either require help from other functions or be a huge change. I&amp;#39;ll explain the reasoning for some of these checks later in the blog post.&lt;/p&gt;
&lt;p&gt;Now that the work with the thesis is over, and I don&amp;#39;t need to think about the thesis&amp;#39; scope anymore, I will update the page and the checklist occasionally. I already have some things in mind from both learning and checking perspectives. Also, if you want to contribute, the checklist website&amp;#39;s front page has a link to a form, which you can answer. &lt;/p&gt;
&lt;p&gt;Before sharing the checklist, I&amp;#39;ll say a few words about two checks/categories I had to make hard decisions about and the reasoning behind the decisions. &lt;/p&gt;
&lt;h2 id=&quot;some-thoughts-on-text-alternatives-and-screen-orientation&quot;&gt;Some Thoughts on Text Alternatives and Screen Orientation&lt;/h2&gt;
&lt;p&gt;The first check I&amp;#39;m going to discuss is related to visual elements and text alternatives. Why is there no check for visual elements having a text alternative? The reason for this is that quite often the actual copy of the text alternative (or content description) is not within the developer&amp;#39;s control. They might not know the intended purpose of the visual, or there might be a copywriter responsible for all the texts in the app. That&amp;#39;s why I&amp;#39;ve included the check for text alternatives in the &amp;quot;Checks that might be out of your control&amp;quot;-section and not the actual checklist. &lt;/p&gt;
&lt;p&gt;The other category I&amp;#39;m discussing is the orientation. I had to think about it a lot. Should I include a check to ensure the orientation is not locked? I wanted to, but I had to keep in mind the fact that the checklist aims to be as easy as possible to integrate, and most Android apps don&amp;#39;t support both portrait and landscape modes. So, I decided not to include that check but rather have it in the &amp;quot;Checks that might be out of your control&amp;quot;-section. &lt;/p&gt;
&lt;h2 id=&quot;the-checklist&quot;&gt;The Checklist&lt;/h2&gt;
&lt;p&gt;Alright, let&amp;#39;s get to the checklist! It&amp;#39;s also available in &lt;a href=&quot;https://android-a11y-checks.netlify.app/checks&quot;&gt;the Checks-section of Android Accessibility Checklist&lt;/a&gt;. &lt;/p&gt;
&lt;h3 id=&quot;automated-tools&quot;&gt;Automated Tools&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Ran Accessibility Scanner for the feature&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/accessibility-scanner&quot;&gt;Read instructions for testing with Accessibility Scanner&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;images-and-other-visual-elements&quot;&gt;Images and Other Visual Elements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Purely decorative images have content description set to null&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;alternative-ways-of-navigation&quot;&gt;Alternative ways of navigation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The feature works with keyboard and D-pad&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/keyboard&quot;&gt;Read instructions for testing with a keyboard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The feature works with Switch Access&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/switch-access&quot;&gt;Read instructions for testing with Switch Access&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The feature works with Voice control&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/voice-access&quot;&gt;Read instructions for testing with Voice Control&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The feature works with a screen reader&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/screen-reader&quot;&gt;Read instructions for testing with a screen reader&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Focus order matches visual representation&lt;/li&gt;
&lt;li&gt;Added accessibility actions for gesture based actions (e.g. swipe or drag and drop), or there is another way to interact than the gestures (e.g. buttons)&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/custom-components&quot;&gt;Read instructions for building custom components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;orientation&quot;&gt;Orientation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Experience is similar for landscape and portrait modes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;semantics-and-structure&quot;&gt;Semantics and Structure&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Semantics (e.g. headings) are identified&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/custom-components&quot;&gt;Read instructions for building custom components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Form elements (such as text fields, radio inputs and switches) have a text label&lt;/li&gt;
&lt;li&gt;Custom components have correct semantics, state information and interaction patterns&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/custom-components&quot;&gt;Read instructions for building custom components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;magnification&quot;&gt;Magnification&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The feature works when using magnification&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/magnification&quot;&gt;Read instructions for testing magnification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The feature works with font size set to the biggest size&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/learning/font-size&quot;&gt;Read instructions for testing font size&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;checks-that-might-be-out-of-your-control-but-worth-checking&quot;&gt;Checks That Might be Out of Your Control (But Worth Checking!)&lt;/h2&gt;
&lt;h3 id=&quot;images-and-other-visual-elements-1&quot;&gt;Images and Other Visual Elements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Every visual element that conveys meaning has a descriptive text alternative&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;use-of-color&quot;&gt;Use of Color&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The app/feature works even if user can&amp;#39;t see colors&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;orientation-1&quot;&gt;Orientation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Orientation is not locked to either mode&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;audio-and-video&quot;&gt;Audio and Video&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Audio and video clips have captions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt; In this blog post, I&amp;#39;ve introduced an Android Accessibility Checklist I developed when writing my Master&amp;#39;s thesis. The checklist and learning materials are available on the &lt;a href=&quot;https://android-a11y-checks.netlify.app/&quot;&gt;Android Accessibility Checklist&lt;/a&gt; site, which also contains a link to a survey if you want to help with further research.&lt;/p&gt;
&lt;p&gt;What&amp;#39;s your relationship with accessibility? Do you consider it when developing?&lt;/p&gt;
&lt;h2 id=&quot;top-level-links-in-the-blog-post&quot;&gt;Top Level Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/&quot;&gt;Android Accessibility Checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://osuva.uwasa.fi/handle/10024/17254&quot;&gt;An Actionable Accessibility Checklist for Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/checks&quot;&gt;the Checks-section of Android Accessibility Checklist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Beyond the Binary - More Inclusive Gender Options with Compose</title>
    <link href="https://eevis.codes/blog/2024-09-02/beyond-the-binary-more-inclusive-gender-options-with-compose/" />
    <updated>2024-09-02T03:38:22.011Z</updated>
    <id>https://eevis.codes/blog/2024-09-02/beyond-the-binary-more-inclusive-gender-options-with-compose/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/6YJIDHFsQHregKq6vdW4IS/dd1ba3b9eeefbe6d58b360bda717d59c/more-inclusive-gender-options-square.png"/>]]>
      &lt;p&gt;If your gender matches the one that was assigned to you at birth, you probably don&amp;#39;t notice flaws in application design that enforce the gender binary. But if it doesn&amp;#39;t, and especially if your gender is beyond the woman-man binary, you constantly notice user interfaces that exclude you. &lt;/p&gt;
&lt;p&gt;For example, what should you select if you aren&amp;#39;t a man or a woman, and those are the only options in a form? In some cases, forms might include the option &amp;quot;other&amp;quot;. But... It underlines that you&amp;#39;re something other and don&amp;#39;t even have a category. As someone who has felt their entire life like an outsider and the other in many ways, I can tell you that it doesn&amp;#39;t do good things to your mental health. &lt;/p&gt;
&lt;p&gt;So, in this blog post, I&amp;#39;ll discuss the concept of gender a bit and then demonstrate one way to build a more inclusive gender selection with Jetpack Compose. Finally, at the end, I&amp;#39;ll list some resources related to this topic.&lt;/p&gt;
&lt;h2 id=&quot;gender-beyond-the-binary&quot;&gt;Gender Beyond the Binary&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s start with the basics: Gender and sex are separate things. In some languages, like Finnish, the word for both is the same - &lt;span lang=&quot;fi&quot;&gt;&amp;quot;sukupuoli&amp;quot;&lt;/span&gt; - but, for example, English differentiates them. And neither of them is binary. &lt;/p&gt;
&lt;p&gt;Gender is a social construct, and each culture has its own norms for genders. There are multiple genders, and a person might describe themself with one or more. Some examples include woman, man, non-binary, agender, genderqueer, demigender (e.g., demigirl or demiboy), or genderfluid.  &lt;/p&gt;
&lt;p&gt;If you want to learn more, I&amp;#39;ve listed some resources in the &lt;a href=&quot;https://eevis.codes/blog/2024-09-02/beyond-the-binary-more-inclusive-gender-options-with-compose/#resources&quot;&gt;Resources-section&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;asking-users-gender&quot;&gt;Asking User&amp;#39;s Gender&lt;/h2&gt;
&lt;p&gt;Gender is asked in many locations - it might be surveys, settings for an app, and many other places. I&amp;#39;ll concentrate on app development as I write about app development, and the specific case of app settings because that&amp;#39;s where I&amp;#39;ve seen this gender selection pop up the most frequently. &lt;/p&gt;
&lt;p&gt; Before diving into the technical implementation of more inclusive gender options, I want to discuss whether there is a need for gender data and making the selection optional. Ruth Dillon-Mansfield discusses these topics, and more, in her article &lt;a href=&quot;https://ruth-dm.co.uk/how-to-ask-about-gender-in-forms-respectfully&quot;&gt;How to Ask About Gender in Forms Respectfully&lt;/a&gt;, which I recommend checking.&lt;/p&gt;
&lt;h3 id=&quot;do-you-actually-need-that-information&quot;&gt;Do You Actually Need That Information?&lt;/h3&gt;
&lt;p&gt;The first question related to asking the user&amp;#39;s gender is whether you really need it. Is the form asking it just because it&amp;#39;s customary to do so, or because the company wants more data for marketing purposes, or is the data used for something the user benefits from? &lt;/p&gt;
&lt;p&gt;When this information is asked, the reasons for requesting it should always be explained. Why is it asked? How is the data used? How it&amp;#39;s stored? So remember to include that information easily available next to the form where this is asked.&lt;/p&gt;
&lt;h3 id=&quot;make-it-optional&quot;&gt;Make It Optional&lt;/h3&gt;
&lt;p&gt;Not everyone is comfortable sharing their gender identity in all instances, even if you have good reasons for asking it. There might be a lot of reasons for that, and those reasons should be respected. &lt;/p&gt;
&lt;h2 id=&quot;more-inclusive-gender-options&quot;&gt;More Inclusive Gender Options&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s look at one way to build a more inclusive gender selection component on Android. &lt;/p&gt;
&lt;p&gt;The component we&amp;#39;re going to build will have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple options beyond the &amp;quot;Man/Woman&amp;quot; binary&lt;/li&gt;
&lt;li&gt;Possibility of not disclosing gender&lt;/li&gt;
&lt;li&gt;A text field to type the gender if the given options don&amp;#39;t match&lt;/li&gt;
&lt;li&gt;Possibility to select more than one option&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The component looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2xjGlT2mUP48L5ZcM1cHll/981118462721a3924c2005061822495e/Screenshot_2024-09-01_at_12.11.45.png&quot; alt=&quot;A form question with a title &amp;#39;Which most accurately describe(s) you?&amp;#39; and options with checkboxes Woman, Man, Non-Binary, Let me type, Prefer not to disclose. Let me type is selected, and under it, there&amp;#39;s a text field with label Type here and typed text Agender.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This list could include more genders, but as this is a demo, I decided to have it as short as possible while still allowing the user to input their gender.&lt;/p&gt;
&lt;p&gt;We will need two UI components: A checkbox and a checkbox with a text field. Let&amp;#39;s build the checkbox first, and then we can use it with the text field:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun CheckboxWithLabel(
    checked: Boolean,
    label: String,
    onCheckedChange: (Boolean) -&amp;gt; Unit
) {
    Row(
        modifier = Modifier.toggleable(
            value = checked,
            role = Role.Checkbox,
            onValueChange = onCheckedChange
        ),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Checkbox(
            modifier = Modifier.clearAndSetSemantics {},
            checked = checked,
            onCheckedChange = onCheckedChange
        )
        Text(label)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component takes in the value for the checkbox checked-state, a function to toggle the value, and a label for the checkbox. Inside the component, we&amp;#39;re wrapping the label and the checkbox together with a &lt;code&gt;Row&lt;/code&gt;, adding a &lt;code&gt;toggleable&lt;/code&gt;-modifier to the parent &lt;code&gt;Row&lt;/code&gt;, and then clearing semantics from the &lt;code&gt;Checkbox&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;We&amp;#39;re doing this for increased accessibility so assistive technology users can use the checkbox more easily. It also extends the touch area to cover the label, too, not just the checkbox component. If you want to read more, I&amp;#39;ve written a blog post about &lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;improving Android accessibility with modifiers in Jetpack Compose&lt;/a&gt;, which explains these decisions more. &lt;/p&gt;
&lt;p&gt;The second component, a checkbox with a text field, takes in the same parameters as the previous component and two additional parameters: Value and value setter for the text field. The code for the component looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun CheckboxWithTextField(
    checked: Boolean,
    label: String,
    textValue: String,
    onTextChange: (String) -&amp;gt; Unit,
    onCheckedChange: (Boolean) -&amp;gt; Unit
) {
    Column {
        CheckboxWithLabel(
            checked = checked, 
            label = label, 
            onCheckedChange = onCheckedChange
        )
        if (checked) {
            OutlinedTextField(
                modifier = Modifier
                    .fillMaxWidth()
                label = {
                      Text(&amp;quot;Type here&amp;quot;)
                },
                value = textValue,
                onValueChange = onTextChange
            )
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&amp;#39;re using the &lt;code&gt;CheckboxWithLabel&lt;/code&gt; component and passing in the required parameters. In addition, if the checkbox is checked, we show an &lt;code&gt;OutlinedTextField&lt;/code&gt;, for which we pass the additional parameters. &lt;/p&gt;
&lt;p&gt;Finally, we use both of these components to display the options. A list of options with the label and field type mapped together is used in this case. The field types are defined as an enum, which is then used in the list:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;enum class OptionType {
    Boolean,
    String
}

val options =
    listOf(
        &amp;quot;Woman&amp;quot; to OptionType.Boolean,
        &amp;quot;Man&amp;quot; to OptionType.Boolean,
        &amp;quot;Non-Binary&amp;quot; to OptionType.Boolean,
        &amp;quot;Let me type&amp;quot; to OptionType.String,
        &amp;quot;Prefer Not to Disclose&amp;quot; to OptionType.Boolean,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh, and it should go without saying: Trans-women are women, and trans-men are men. If you want to list them separately, the other options would be &amp;quot;Cis-woman&amp;quot; and &amp;quot;Cis-man&amp;quot;. A cis-person is someone whose gender conforms to the one assigned at birth to them.&lt;/p&gt;
&lt;p&gt;In the parent component, we&amp;#39;re mapping through the options and then setting the correct component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;options.map { (label, type) -&amp;gt;
    when (type) {
        OptionType.Boolean -&amp;gt;
            CheckboxWithLabel(...)
        OptionType.String -&amp;gt;
            CheckboxWithTextField(...)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example, I&amp;#39;ve used a component-level state to store the selected values and the text value. In an actual application, the values would be stored somewhere permanently (like a database), so I&amp;#39;m leaving that part out of this blog post. &lt;/p&gt;
&lt;p&gt;The complete code I&amp;#39;ve used is in this &lt;a href=&quot;https://gist.github.com/eevajonnapanula/795683e13d4d0211c82e97938428790d&quot;&gt;Github gist&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve discussed making gender options more inclusive beyond the binary. I hope that after reading this blog post, you&amp;#39;ll remember at least these things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gender options should be more diverse than just &amp;quot;Man&amp;quot; and &amp;quot;Woman&amp;quot;.&lt;/li&gt;
&lt;li&gt;Disclosing gender should be optional.&lt;/li&gt;
&lt;li&gt;The reason why gender information is asked should always be explained.&lt;/li&gt;
&lt;li&gt;It should be possible to select multiple options, and there should be option for self-description.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you for reading!&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.selfdefined.app/definitions/non-binary/&quot;&gt;Non-binary definition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gender.fandom.com/wiki/Gender_Wiki&quot;&gt;Gender Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://alistapart.com/article/trans-inclusive-design/&quot;&gt;Trans Inclusive Design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fossheim.io/writing/posts/non-binary-design/&quot;&gt;Navigating the internet as a non-binary designer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thesocietypages.org/trot/2018/11/26/the-social-construction-of-gender-and-sex/&quot;&gt;The Social Construction of Gender and Sex&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;other-links-in-the-blog-post&quot;&gt;Other Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ruth-dm.co.uk/how-to-ask-about-gender-in-forms-respectfully&quot;&gt;How to Ask About Gender in Forms Respectfully&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-11/improving-android-accessibility-with-modifiers-in-jetpack-compose/&quot;&gt;Improving Android accessibility with modifiers in Jetpack Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/eevajonnapanula/795683e13d4d0211c82e97938428790d&quot;&gt;Github gist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Testing Android App Accessibility: Clue</title>
    <link href="https://eevis.codes/blog/2024-09-15/testing-android-app-accessibility-clue/" />
    <updated>2024-09-15T03:27:11.064Z</updated>
    <id>https://eevis.codes/blog/2024-09-15/testing-android-app-accessibility-clue/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/5gOCmr6befzJTPIvrjCC7G/2083413a5f01f87b3dd6d94227e2c317/testing-android-app-accessibility-clue-square.png"/>]]>
      &lt;p&gt;In Droidcon Berlin, I gave a talk titled &amp;quot;Is This App Accessible? A Live Testing Demo,&amp;quot; in which I tested one app from an accessibility perspective. The audience voted for the app from three options and selected Clue, a period tracking app. The other two were the Droidcon app and Provinssi&amp;#39;s (a Finnish music festival I attended in June) app. &lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;ll summarize the talk and probably add some things I was supposed to say on stage but forgot. So note that this post might slightly differ from the actual talk I gave. If you want to watch the talk, it&amp;#39;s available on the Droidcon website: &lt;a href=&quot;https://www.droidcon.com/2024/08/30/is-this-app-accessible-a-live-testing-demo/&quot;&gt;Is this app accessible? A live testing demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Note that as the talk was 40 minutes long, I did not do that extensive accessibility testing, and the app may have more issues than I found. &lt;/p&gt;
&lt;h2 id=&quot;why-i-chose-clue&quot;&gt;Why I Chose Clue?&lt;/h2&gt;
&lt;p&gt;When I was thinking about the possible apps for testing, I wanted to include an app that&amp;#39;s not the most typical example in developer conferences and learning materials. I can&amp;#39;t count how many examples of different vehicles I&amp;#39;ve seen (hello, inheritance!). Or examples having something related to movies. &lt;/p&gt;
&lt;p&gt;As the gender balance in tech is skewed towards those who don&amp;#39;t have cycles, I wanted to bring this topic in. I also think that periods should be talked about way more in different settings - that would help to dismantle the taboo around them, as well as all those stupid stereotypes and jokes around menstruating people. &lt;/p&gt;
&lt;p&gt;And why Clue, why not some other app? It&amp;#39;s because their app is the best one. Their app is the least heteronormative I&amp;#39;ve encountered out there. It doesn&amp;#39;t assume my gender, nor does it assume the gender of the partner if I wanted to share my cycle data with someone. &lt;/p&gt;
&lt;p&gt;And speaking of the partner mode... I haven&amp;#39;t used it, but I haven&amp;#39;t also seen any creepy &amp;quot;we will let your (man) partner know that based on your cycle, you want to have sex&amp;quot;-messages. I&amp;#39;m not naming names, but some other period-tracking apps do this. &lt;/p&gt;
&lt;p&gt;Note! Clue didn&amp;#39;t pay me to advertise them! These opinions are purely based on my own experiences and thoughts. I just really like the application.&lt;/p&gt;
&lt;p&gt; I was (positively) surprised that the audience selected Clue for testing. I was pretty sure everyone would have wanted to see me break the conference app. Maybe next time? &lt;/p&gt;
&lt;h2 id=&quot;about-the-tests&quot;&gt;About the Tests&lt;/h2&gt;
&lt;p&gt;The tools and  settings I used during the talk were the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accessibility Scanner &lt;/li&gt;
&lt;li&gt;Switch Access&lt;/li&gt;
&lt;li&gt;Larger font size and display&lt;/li&gt;
&lt;li&gt;Remove animations setting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For those of you who are not familiar with these tools and settings, here&amp;#39;s a short description of each of them: &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Accessibility Scanner&lt;/strong&gt; is an app for semi-automated accessibility testing, and it can detect accessibility issues in the following categories: Content labels, Touch target size, Clickable items, and Text and image contrast. It&amp;#39;s available in Google Play, so you can test any Android app with it - even without access to source code. I have written a blog post on how to test with Accessibility Scanner: &lt;a href=&quot;https://eevis.codes/blog/2024-02-16/testing-with-accessibility-scanner/&quot;&gt;Testing with Accessibility Scanner&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Switch Access&lt;/strong&gt; is a service that lets a user use their phone with one or more switches. Usually, when two switches are in use, one is used for navigating forward, and the other is used for completing actions. Google has released a video on how to set up Switch Access, so if you want to check it out, it&amp;#39;s available in YouTube: &lt;a href=&quot;https://www.youtube.com/watch?v=tLIUaZyTtX4&quot;&gt;Switch Access for developers - Accessibility on Android&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Larger font size and display&lt;/strong&gt; are both set from the phone&amp;#39;s accessibility settings. You can control both font and display sizes, which is useful, especially when testing for accessibility issues with larger phones, while many users have smaller ones. For the testing purposes, I usually crank them both to the largest to find the most of the problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remove animations-setting&lt;/strong&gt; is an accessibility setting, which removes animations as the name states. On some operating systems, the setting is called &amp;quot;Reduce motion&amp;quot;, and I often keep referring to it as such. I&amp;#39;ve written about the setting for Android developers: &lt;a href=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Android, Animations and Reduced motion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From these four, I actively tested the first three; for the remove animations-setting testing came sort of for free, as I always have it on. &lt;/p&gt;
&lt;h2 id=&quot;accessibility-scanner&quot;&gt;Accessibility Scanner&lt;/h2&gt;
&lt;p&gt;When testing with the Accessibility Scanner, I tested three screens: The main screen with the main view showing the current cycle data, then the menu, and finally, the Analysis-page. The app took the screenshot from the Analysis-page before the page had loaded, so there was not much useful findings, but the other two revealed some issues. &lt;/p&gt;
&lt;p&gt;Here&amp;#39;s a screenshot of the main screen with annotated issues. Note, that my font size was already a bit bigger than the default, so that&amp;#39;s why the &amp;quot;Try now for free&amp;quot;-button looks a bit odd. We&amp;#39;ll get back to that in the font size section. &lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/6nvT4yqr8YLQTXLE9jmNEk/4ac9dad6ff739d73bb763201516f749f/screen1.png&quot; alt=&quot;Clue app&#39;s home page with orange rectangles around different items on screen.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;The picture displays an orange rectangle around the text, like the button mentioned above, and the text &amp;quot;Subscribe to Clue Plus and support our mission&amp;quot; is next to it, as well as the &amp;quot;Day&amp;quot;-text above the current cycle day and the text &amp;quot;About the follicular phase&amp;quot;. The drop of blood-icon also has an orange rectangle around it. &lt;/p&gt;
&lt;p&gt;On the Accessibility Scanner app, I can click each of these items to get more information. For example, the drop of blood-icon reveals the following screen, describing that that item may not have a label readable by screen readers:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/34FSNqcHvUi5vwCPld4Wzi/36846fbab1c55f4f8a72ce2eeb7ca9c6/Screenshot_20240712-082303__1_.png&quot; alt=&quot;A more close look on the screen with the blood icon bordered with orange rectangle, and more details on the problems on the bottom of the page. &quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;Why is it a problem? This element is interactive (a button, even though it doesn&amp;#39;t really look like one), and if a person who navigates with a screen reader focuses on it, they hear that it&amp;#39;s an unlabeled button. They probably have no idea of what they can do with that element. &lt;/p&gt;
&lt;p&gt;The Accessibility Scanner app also lets us see all the issues from different screens, categorized by screen or category. For this test run, we can see that there were five categories:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/6uuDP9As3iMSi0UyYk9EdS/bbaec3328d48502053728101879c14fa/Screenshot_20240808_064520.png&quot; alt=&quot;Screen with title &#39;All suggestions&#39;, and then closed accordions with texts Item label (1), Text contrast (26), Unexposed text (7), Item descriptions (1), and Image contrast (1).&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;Most issues were with text contrast; for example, all of the annotated texts on the main screen had some level of text contrast issue. &lt;/p&gt;
&lt;h2 id=&quot;switch-access&quot;&gt;Switch Access&lt;/h2&gt;
&lt;p&gt;I also tested the app with Switch Access. I found two main issues during the short testing: I couldn&amp;#39;t switch days from the visualization with the current cycle day visible, and the &amp;quot;Track&amp;quot;-button in the bottom bar didn&amp;#39;t work. &lt;/p&gt;
&lt;p&gt;The first issue has probably something to do with the fact that it&amp;#39;s most likely built with canvas. Adding navigation to the canvas is not straightforward because you can&amp;#39;t make individual items inside the canvas, e.g., focusable. You can add pointer tracking easily, but you can&amp;#39;t add focusability, etc. &lt;/p&gt;
&lt;p&gt;I suggest adding controls, like buttons, that would control the currently visible item so that anyone who can&amp;#39;t use a pointer input could control the visible item with those. I&amp;#39;ve explained this topic in my blog post &lt;a href=&quot;https://eevis.codes/blog/2023-12-09/more-accessible-graphs-with-jetpack-compose-part-4-on-screen-control-buttons/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 4: On-Screen Control Buttons&lt;/a&gt;, so if you want to read more, head there. &lt;/p&gt;
&lt;p&gt;The other problem is the &amp;quot;Track&amp;quot;-button not being reachable with switch access. While the drop of blood icon leads to tracking, the same place where the unreachable &amp;quot;Track&amp;quot;-button does, it&amp;#39;s not clearly marked as a button, and it doesn&amp;#39;t have a label, so it can be hard to know where it leads. &lt;/p&gt;
&lt;p&gt;Solving that would require finding out why it&amp;#39;s not reachable and making it accessible with a switch device.  &lt;/p&gt;
&lt;h2 id=&quot;largest-font-size-and-display-size&quot;&gt;Largest Font Size and Display Size&lt;/h2&gt;
&lt;p&gt;My last test was turning font and display sizes up to the maximum. As you can see from the picture, not everything worked:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/5dAgtlgxvJDz8PeDHmkYMo/8165648778ea9493bdeabe03eb545b0f/Screenshot_20240808_064824.png&quot; alt=&quot;Clue&#39;s homepage with large font and display sizes. Texts scale either on top of other elements or they cut and are unreadable.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;The screen above has multiple problems. The biggest, I&amp;#39;d say, is that in many places, texts are cut out, and it&amp;#39;s not clear what they say. For example, the bottom bar texts are far from clear - and if you don&amp;#39;t do well with iconography (meaning, you don&amp;#39;t always understand what each icon means), it might be hard to navigate in this app without knowing where the bottom bar item leads. &lt;/p&gt;
&lt;p&gt;How do we fix these problems? Usually, components are not scaling because some elements are fixed in size or position. It leads to either the fixed size/position component not scaling well or affecting other components around. So, the actual fix depends on the underlying problem. The ultimate goal is to have all the information available for users who turn the font and/or display sizes up. &lt;/p&gt;
&lt;h2 id=&quot;remove-animations--setting&quot;&gt;Remove Animations -Setting&lt;/h2&gt;
&lt;p&gt;I did not actively try to find anything related to the Remove animations setting. My strategy was that if I encounter animations, then I know this setting is not working. I did not notice anything when testing different app screens, so I&amp;#39;d say Clue passed this test very well. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, I&amp;#39;ve discussed accessibility testing of the Clue app. While there were problems, I must say that I&amp;#39;ve seen way worse apps out there from the accessibility point of view.&lt;/p&gt;
&lt;p&gt;The tests I did were something we, as developers, can (and should!) do while developing. Once you start testing and do it a couple of times, they become straightforward - the most challenging part is actually remembering to test for accessibility. &lt;/p&gt;
&lt;p&gt;Do you test for accessibility? Or have you learned something from this blog post? &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.droidcon.com/2024/08/30/is-this-app-accessible-a-live-testing-demo/&quot;&gt;Is this app accessible? A live testing demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-02-16/testing-with-accessibility-scanner/&quot;&gt;Testing with Accessibility Scanner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=tLIUaZyTtX4&quot;&gt;Switch Access for developers - Accessibility on Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Android, Animations and Reduced motion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-12-09/more-accessible-graphs-with-jetpack-compose-part-4-on-screen-control-buttons/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 4: On-Screen Control Buttons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Paint the Stars - Drawing with Compose and Canvas</title>
    <link href="https://eevis.codes/blog/2024-09-26/paint-the-stars-drawing-with-compose-and-canvas/" />
    <updated>2024-09-26T06:30:01.926Z</updated>
    <id>https://eevis.codes/blog/2024-09-26/paint-the-stars-drawing-with-compose-and-canvas/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/192plm4SGpGBXMc4uBPNn9/e9bf5b7e19e4ca464c69927c2e04454a/paint-the-stars-square.png"/>]]>
      &lt;p&gt;I&amp;#39;ve always struggled with drawing in Canvas, not just with Compose&amp;#39;s Canvas but with any technology. I&amp;#39;ve understood enough to be able to do the tasks I needed to do, but at the same time, I&amp;#39;ve tried to avoid working with it. &lt;/p&gt;
&lt;p&gt;On one Sunday, I wanted to do something creative. I also wanted to code, so I started with the idea of creating a loading spinner with Canvas. One thing led to another, and I made a nice animation, that&amp;#39;s more of a piece of illustration than a loading spinner: &lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/3IU97oa4kVlGC3e1ZDvwhW/5d4f74194c0b91d36a8adf0573faa16f/1726400252028198.mp4&quot; controls=&quot;&quot;&gt; &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;In this and the following blog post, I&amp;#39;ll share how I did it and some things I learned along the way. The first blog post is about drawing the elements on canvas, and the following is about animations.&lt;/p&gt;
&lt;h2 id=&quot;the-idea&quot;&gt;The Idea&lt;/h2&gt;
&lt;p&gt;The initial idea for the space-themed illustration came from a t-shirt I have. It&amp;#39;s this &lt;a href=&quot;https://thespark.company/en-fi/products/i-need-space-feminist-t-shirt&quot;&gt;I Need Space t-shirt from the Spark Company&lt;/a&gt;. I initially thought I could try to reproduce the same picture, but once I was done with the Saturnus, I decided to go rogue.&lt;/p&gt;
&lt;p&gt;I chose to add other elements and make the stars differently. I&amp;#39;m delighted with how it turned out! Let&amp;#39;s dive into creating the illustration next. You can find the &lt;a href=&quot;https://gist.github.com/eevajonnapanula/5e75e960605de7504fb5ed9f5b96b3b9&quot;&gt;complete code for this blog post from this code snippet&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;components&quot;&gt;Components&lt;/h2&gt;
&lt;p&gt;Before talking about the components, I want to note something: The exact numbers I&amp;#39;m showing here fit this particular drawing and can&amp;#39;t be generalized for the most part. I&amp;#39;ve calculated the positions and sizes based on the defined canvas, not for a resizeable canvas.&lt;/p&gt;
&lt;p&gt;The first component is the background and canvas itself. There&amp;#39;s a &lt;code&gt;Column&lt;/code&gt; as the parent component and then &lt;code&gt;Canvas&lt;/code&gt; as a child. This lets us define the animations and other properties you can&amp;#39;t define in &lt;code&gt;Canvas&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;For drawing the background, we&amp;#39;ll use a &lt;code&gt;drawBehind&lt;/code&gt;-modifier in the parent &lt;code&gt;Column&lt;/code&gt;, draw a round rectangle and define a radial gradient brush with some colors: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Modifier.drawBehind {
    drawRoundRect(
        size = size,
        cornerRadius = CornerRadius(44f),
        brush =
        Brush.radialGradient(
            colors = listOf(
                Color(0xFF02010a),
                Color(0xFF04052e),
                Color(0xFF140152),
                Color(0xFF22007c),
                Color.Transparent,
            ),
            radius = size.width * 0.75f,
        ),
    )
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, the drawing looks like this: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/7vDNhQB3w8Y3PsfPsvcmF8/2aab77d6e7b504afa0ba3e676ab4e599/ineedspace-background.png&quot; alt=&quot;Rectangle with radial gradient starting from the center with dark blue and getting lighter towards the edges.&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;saturn&quot;&gt;Saturn&lt;/h3&gt;
&lt;p&gt;The first planet we&amp;#39;re going to add is Saturn. To draw it, we&amp;#39;ll need three elements: The outline of the planet, the rim, and the line inside the planet. Let&amp;#39;s define an extension function, &lt;code&gt;drawSaturn&lt;/code&gt;, which takes in the center offset of the planet and the outline style and draw the outline: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun DrawScope.drawSaturn(
    center: Offset,
    outlineStyle: Stroke,
) {
    drawCircle(
        center = center,
        color = Colors.white,
        radius = 100f,
        style = outlineStyle,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&amp;#39;s a circle, which utilizes the center coordinates and outline style (which is, by the way, a stroke with a width of 4f). We also set a radius for it; in this case, it&amp;#39;s a hardcoded value of 100f. &lt;/p&gt;
&lt;p&gt;Next, we draw the line inside the planet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawArc(
    topLeft = Offset(
        center.x - 80f, 
        center.y - 80f
    ),
    color = Colors.white,
    startAngle = 180f,
    sweepAngle = 90f,
    useCenter = false,
    style =
        Stroke(
            width = 2f,
        ),
    size = Size(160f, 160f),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For that, we use the &lt;code&gt;drawArc&lt;/code&gt;-function. It&amp;#39;s a bit different from the &lt;code&gt;drawCircle&lt;/code&gt;-function, which takes in the center coordinates and radius. For an arc, we need to define the top left coordinate for the rectangle area around the arc and the size of the arc. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;startAngle&lt;/code&gt; defines the angle where the drawing starts, and the &lt;code&gt;sweepAngle&lt;/code&gt; for how long it continues. Position 0 for the angles is at three o&amp;#39;clock, so we want to start at &lt;code&gt;180f&lt;/code&gt; degrees, and the sweep angle is 90f to accomplish the look we want. &lt;/p&gt;
&lt;p&gt;We must also set the &lt;code&gt;useCenter&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;; otherwise, it would draw an extra line through the center. &lt;/p&gt;
&lt;p&gt;The last element to complete the Saturn is the rim. As it&amp;#39;s not a complete circle, we&amp;#39;re using &lt;code&gt;drawArc&lt;/code&gt;. The code looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;rotate(40f, center) {
    drawArc(
        color = Colors.white,
        startAngle = 217f,
        sweepAngle = 285f,
        useCenter = false,
        topLeft = Offset(
            center.x - 50f, 
            center.y - 150f
        ),
        style = outlineStyle,
        size =
            Size(100f, 300f),
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the size of the area where the arc is drawn is not square -  because of the shape of the rim, we need to use a non-square size to make it more like an ellipsis than a circle. &lt;/p&gt;
&lt;p&gt;At this point, the drawing looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2TTPttYyfBICfPYyJG73Jf/444bd4ac8d5a9032ef5580dbac1d0902/ineedspace-saturn.png&quot; alt=&quot;At the top left corner of the rectangle, there is now a planet with a rim around it. The planet takes about a quarter of the rectangle. &quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;planet&quot;&gt;Planet&lt;/h3&gt;
&lt;p&gt;The next element to add is another planet. I didn&amp;#39;t want to name the planet, and I wanted to make it more generic, so we&amp;#39;ll just call it a planet.&lt;/p&gt;
&lt;p&gt;The planet has two elements: The planet itself and the moon orbiting around it. Let&amp;#39;s start with the planet&amp;#39;s outline, which is a circle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
drawCircle(
    center = center,
    color = Colors.white,
    radius = 80f,
    style = outlineStyle,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code is straightforward; we draw a circle into the position defined by the &lt;code&gt;center&lt;/code&gt;-variable, and with a radius of 80. Next, we draw the lines in the planet. For that, we use three paths, two of which have a dashed path effect, and one is a solid line. The code for the first line (the topmost) looks like this: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawPath(
    path =
        Path().apply {
            moveTo(center.x - 82f, center.y)
            quadraticTo(
                center.x - 45f, center.y + 5f, 
                center.x, center.y
            )
        },
    color = Colors.white,
    style =
        Stroke(
            width = 3f,
            pathEffect = PathEffect.dashPathEffect(
                floatArrayOf(60f, 10f, 50f, 10f), 
                0f
            ),
        ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The others are similar; the numbers and path effects are slightly different. You can find the link to the complete code at the start of the blog post.&lt;/p&gt;
&lt;p&gt;So, we&amp;#39;re drawing a path from the edge of the circle towards the center. As the line needs to be slightly curvy, we&amp;#39;re using &lt;code&gt;quadraticTo&lt;/code&gt; to accomplish the effect. &lt;/p&gt;
&lt;p&gt;The other component of the planet is the moon. First, it has the moon drawn as a circle:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawCircle(
    center = Offset(
            center.x - 100f, 
            center.y + 80f
     ),
    color = Colors.white,
    radius = 15f,
    style = outlineStyle,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then, for the line that makes it look like the moon is orbiting around the planet, we use &lt;code&gt;drawArc&lt;/code&gt;: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawArc(
    topLeft = Offset(center.x - 125f, center.y - 125f),
    color = Colors.white,
    startAngle = 160f,
    sweepAngle = 200f,
    useCenter = false,
    style =
        Stroke(
            width = 2f,
            pathEffect =
                PathEffect.dashPathEffect(
                    floatArrayOf(160f, 70f, 50f, 80f, 40f, 40f),
                    0f,
                ),
        ),
    size = Size(250f, 250f),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&amp;#39;s nothing new with the arc - it takes in the top left coordinates relative to the moon. The start angle is 160 degrees, and it goes around 200 degrees. It doesn&amp;#39;t use the center to avoid the third line through the center, and it has &lt;code&gt;dashedPathEffect&lt;/code&gt; to create the look. &lt;/p&gt;
&lt;p&gt;At this point, the drawing looks like this: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/3TLYkNZyuKOXVMQ30hNgEt/746c584ad536df17a157fa7cc0f0e13c/ineedspace-planet.png&quot; alt=&quot;The illustration has now a planet with three lines from the left edge towards center. It also has a moon going around it, with a line going around for about 200 degrees. The Planet is positioned at the bottom right corner. &quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;stars&quot;&gt;Stars&lt;/h3&gt;
&lt;p&gt;The final elements of the illustration are the stars. Let&amp;#39;s create an extension function for &lt;code&gt;DrawScope&lt;/code&gt; to draw a star and then use it to draw all the stars. &lt;/p&gt;
&lt;p&gt;The function takes in the size of the star, center offset, color of the star, and outline style. Let&amp;#39;s define it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun DrawScope.drawStar(
    starSize: Size,
    center: Offset,
    color: Color,
    outlineStyle: Stroke,
) {
   ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&amp;#39;re using a path with quadratic bezier curves from one point of the start to another. The star has four points, and if we imagine the space for a star as a square, the points are in the middle of the outlines. Let&amp;#39;s start from the middle of the top side: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val path = Path()

path.moveTo(center.x, center.y - starSize.height * 0.5f)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, we want to draw a quadratic bezier curve from one point to another and use the center of the area for the star as the curve point. For the line from the top side to the right side, it would look like this: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;path.quadraticTo(
    center.x,
    center.y,
    center.x + starSize.width / 2,
    center.y,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first two parameters are the coordinates for the control point, and the latter two are the point where the line ends. The other three lines look the same - just the end coordinates change based on the target coordinates. &lt;/p&gt;
&lt;p&gt;After drawing those four lines, we just need to draw the path:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawPath(
    path = path,
    color = color,
    style = outlineStyle,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have the &lt;code&gt;drawStar&lt;/code&gt;-function, we can draw the stars. We want to place them around the planets, and we want them to have different colors. We&amp;#39;ll also need to store the size of the star. Let&amp;#39;s define a data class to handle all this: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;data class Star(
    val size: Float,
    val topLeft: Offset,
    val color: Color,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we can define a list of stars. It would look something like this, with an example of one star in a list:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val starsList =
    listOf(
        Star(
            size = 30f,
            topLeft = Offset(size.width * 0.5f, size.height * 0.5f),
            color = Colors.stars[0],
        ),
    ...
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can define different star sizes and have a couple of different colors. The position for each star is calculated manually. &lt;/p&gt;
&lt;p&gt;We can then use the list of stars and draw them with the function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;starsList.forEach { (starSize, offset, color) -&amp;gt;
    drawStar(
        starSize = Size(starSize, starSize),
        color = color,
        center = offset,
        outlineStyle =
            Stroke(
                width = 2f,
            ),
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After these code changes, the illustration looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1tezRBWerwMzaGDOn2znSL/e09ae4fcd732ec16ac021a16c7a97963/ineedspace-stars.png&quot; alt=&quot;The illustration has now stars, that are in three sizes and in three colors all around the planets. &quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve created an illustration with Canvas. It is currently static, but in the following blog post, we will animate the items in the illustration.&lt;/p&gt;
&lt;p&gt;How do you feel about your skills with Canvas? Are you confident, or is it something you&amp;#39;re avoiding altogether?&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://thespark.company/en-fi/products/i-need-space-feminist-t-shirt&quot;&gt;I Need Space t-shirt from the Spark Company&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/eevajonnapanula/5e75e960605de7504fb5ed9f5b96b3b9&quot;&gt;complete code for this blog post from this code snippet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Floating in Space - Animations with Compose and Canvas</title>
    <link href="https://eevis.codes/blog/2024-10-06/floating-in-space-animations-with-compose-and-canvas/" />
    <updated>2024-10-06T10:17:03.902Z</updated>
    <id>https://eevis.codes/blog/2024-10-06/floating-in-space-animations-with-compose-and-canvas/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3QzAyrr43ALq5Cl67CtiWB/2393aa8c809a31173e0b693551a14102/floating-in-space-square.png"/>]]>
      &lt;p&gt;In my previous blog post, &lt;a href=&quot;https://eevis.codes/blog/2024-09-26/paint-the-stars-drawing-with-compose-and-canvas/&quot;&gt;Paint the Stars — Drawing with Compose and Canvas&lt;/a&gt;, I shared how I wanted to be better with Canvas and Compose and created an illustration with planets and stars. This blog post shares how to animate those elements. In the end, the result will look like this:&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/3IU97oa4kVlGC3e1ZDvwhW/5d4f74194c0b91d36a8adf0573faa16f/1726400252028198.mp4&quot; controls=&quot;&quot;&gt; &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/eevajonnapanula/5e75e960605de7504fb5ed9f5b96b3b9&quot;&gt;Full code is available in this snippet&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;animations&quot;&gt;Animations&lt;/h2&gt;
&lt;h3 id=&quot;stars&quot;&gt;Stars&lt;/h3&gt;
&lt;p&gt;Let&amp;#39;s start with the stars. In the previous blog post, we defined the &lt;code&gt;Star&lt;/code&gt; data class like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;data class Star(
    val size: Float,
    val topLeft: Offset,
    val color: Color,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From these values, we want to animate the size of the stars. We&amp;#39;ll do it by defining a multiplier that we will use with the star sizes to create the effect of the stars twinkling. We do this with &lt;code&gt;infiniteTransition&lt;/code&gt; and &lt;code&gt;animateFloat&lt;/code&gt;: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val infiniteTransition = 
    rememberInfiniteTransition(label = &amp;quot;infinite&amp;quot;)

val starSizeMultiplierOne by infiniteTransition
    .animateFloat(
        initialValue = 1f,
        targetValue = 1.5f,
        animationSpec =
            infiniteRepeatable(
                animation =
                    tween(
                        durationMillis = 3000,
                        easing = animationEasing,
                    ),
                repeatMode = RepeatMode.Reverse,
            ),
        label = &amp;quot;starSizeMultiplierOne&amp;quot;,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the &lt;code&gt;starSizeMultiplierOne&lt;/code&gt;, we give the initial value of 1, meaning that as it will act as a multiplier, the size would be 1 * size. The target value is 1.5, and as the repeat mode is &lt;code&gt;Reverse&lt;/code&gt;, the float will animate between the size multiplied by 1 and 1.5. This transition creates a growing and shrinking effect. &lt;/p&gt;
&lt;p&gt;As we want the stars to look realistic and not animate at the same speed, we need to define two animated multipliers. Let&amp;#39;s define another one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val starSizeMultiplierTwo by infiniteTransition
    .animateFloat(
        initialValue = 0.7f,
        targetValue = 1.7f,
        animationSpec =
            infiniteRepeatable(
                animation =
                    tween(
                        durationMillis = 2300,
                        easing = animationEasing,
                    ),
                repeatMode = RepeatMode.Reverse,
            ),
        label = &amp;quot;starSizeMultiplierTwo&amp;quot;,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one has a bit shorter animation duration, and the initial and target values are different, so the twinkling happens at a different rate from the first multiplier. &lt;/p&gt;
&lt;p&gt;Now we&amp;#39;ll just need to pass these multipliers to stars:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val stars =
    starsList.mapIndexed { index, star -&amp;gt;
        val multiplier = if (index % 2 == 0) 
            starSizeMultiplierOne 
        else 
            starSizeMultiplierTwo

        star.copy(
            size = star.size * multiplier,
        )
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We map through the list of stars defined previously, return copies of the star with the multiplier attached, and store them in a new list. Later, when drawing the stars, we use this new list. &lt;/p&gt;
&lt;p&gt;In the case of the code snippet, this mapping through the list might feel redundant - why just not put the multipliers in when defining the stars? Well, in this case, it would work. But if we defined the stars outside the component, animated values can be hard to attach, so this would be the strategy. &lt;/p&gt;
&lt;h3 id=&quot;planet&quot;&gt;Planet&lt;/h3&gt;
&lt;p&gt;The next thing we want to animate is the planet - or the moon orbiting around it, to be exact. &lt;/p&gt;
&lt;p&gt;In the previous post, we defined how to draw the planet and the moon, and the source code showed an extension function called &lt;code&gt;drawMoon&lt;/code&gt;. Let&amp;#39;s extend it and pass in a parameter called &lt;code&gt;degrees&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun DrawScope.drawMoon(
    center: Offset,
    outlineStyle: Stroke,
    degrees: Float,
) {
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we wrap the contents of &lt;code&gt;drawMoon&lt;/code&gt; with a &lt;code&gt;rotate&lt;/code&gt;, which uses the &lt;code&gt;degrees&lt;/code&gt; for rotation and &lt;code&gt;center&lt;/code&gt; for pivot so that the rotation happens around the center of the planet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;rotate(degrees = degrees, pivot = center) {
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the top-level component, we define the degrees, which is an animated float value: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val degrees by infiniteTransition
    .animateFloat(
        initialValue = 360f,
        targetValue = 0f,
        animationSpec =
            infiniteRepeatable(
                animation =
                    tween(
                        durationMillis = 3000 * 6,
                        easing = LinearEasing,
                    ),
            ),
        label = &amp;quot;degrees&amp;quot;,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the initial value, we use 360f, and as the target value, 0f. These values create the effect that the moon orbits counter-clockwise around the planet.&lt;/p&gt;
&lt;h3 id=&quot;saturn&quot;&gt;Saturn&lt;/h3&gt;
&lt;p&gt;The last item we animate is Saturn. Its movement is subtle - slightly moving up and down to create a floating effect. &lt;/p&gt;
&lt;p&gt;In the previous blog post, we defined an extension function, &lt;code&gt;drawSaturn&lt;/code&gt;, that takes in top-left coordinates and outline style. We can use the top-left coordinates for the effect. &lt;/p&gt;
&lt;p&gt;First, let&amp;#39;s define center offset, an animated float value we&amp;#39;re going to use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val centerOffset by infiniteTransition
    .animateFloat(
        initialValue = 0f,
        targetValue = 2f,
        animationSpec =
            infiniteRepeatable(
                animation =
                    tween(
                        durationMillis = 3000,
                        easing = EaseIn,
                    ),
                repeatMode = RepeatMode.Reverse,
            ),
        label = &amp;quot;centerOffset&amp;quot;,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the initial value is 0f, and the target value is 2f. We can then use this and change the parameters we pass to &lt;code&gt;drawSaturn&lt;/code&gt; by adding the &lt;code&gt;centerOffset&lt;/code&gt; to the top-left coordinates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawSaturn(
    center =
        Offset(
            size.width * 0.25f + centerOffset,
            size.height * 0.25f + centerOffset
        ),
    outlineStyle = outlineStyle,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, the x and y coordinates have an extra amount of animating from 0f to 2f, creating the floating effect.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve covered animating drawings on Canvas. All the animations used animated floats in different ways. And as we can see, small changes add a lot of movement to the Canvas. &lt;/p&gt;
&lt;p&gt;I hope you&amp;#39;ve enjoyed this blog post and learned something!&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-09-26/paint-the-stars-drawing-with-compose-and-canvas/&quot;&gt;Paint the Stars — Drawing with Compose and Canvas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/eevajonnapanula/5e75e960605de7504fb5ed9f5b96b3b9&quot;&gt;Full code is available in this snippet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>First Impressions of Compose Multiplatform</title>
    <link href="https://eevis.codes/blog/2024-10-17/first-impressions-of-compose-multiplatform/" />
    <updated>2024-10-17T13:43:23.746Z</updated>
    <id>https://eevis.codes/blog/2024-10-17/first-impressions-of-compose-multiplatform/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/449fjAxqNUUR2jTOyssvX/e6e07c8535dee8e398fb0883a356ee20/cpm-square.png"/>]]>
      &lt;p&gt;I finally had the time to dive into &lt;a href=&quot;https://www.jetbrains.com/compose-multiplatform/&quot;&gt;Compose Multiplatform&lt;/a&gt; - and this was also my first experience with &lt;a href=&quot;https://kotlinlang.org/docs/multiplatform.html&quot;&gt;Kotlin Multiplatform&lt;/a&gt;. I&amp;#39;ll share my first impressions from using this technology in this blog post.&lt;/p&gt;
&lt;p&gt;Some words from my background: I&amp;#39;m an Android developer who&amp;#39;s confident with Kotlin and Compose. I&amp;#39;ve been working with different languages and frameworks before switching to Android development, so I could say learning new things is easy. However, I&amp;#39;ve never done any iOS development, so that part is entirely new to me. &lt;/p&gt;
&lt;p&gt;The app I&amp;#39;m working with is currently just UI, and I have yet to dive into any architectural components, such as working with databases or view models. So, that&amp;#39;s the context for these thoughts, and it&amp;#39;s likely that once I get deeper into the development and concepts, I&amp;#39;ll have more opinions and thoughts.&lt;/p&gt;
&lt;h2 id=&quot;the-app-im-building&quot;&gt;The App I&amp;#39;m Building&lt;/h2&gt;
&lt;p&gt;The moment I started writing the blog post, I realized that it might be helpful to talk about the app I&amp;#39;m building here. So here we go. &lt;/p&gt;
&lt;p&gt;It&amp;#39;s an app version of &lt;a href=&quot;https://neule.art/&quot;&gt;neule.art&lt;/a&gt;, a color picker for knitted sweaters I created a few years ago. In the first version, there&amp;#39;s just the picture of the sweater (the same as on the website) and modals opening for choosing the color from the list of colors. The colorways are based on the colors of Istex Lettlopi, a yarn often used for Icelandic sweaters. &lt;/p&gt;
&lt;p&gt;When I created the website, I hand-drew the picture on top of an image of my friend wearing a sweater and then turned it into an SVG. In the app, I used Canvas to replicate the illustration. Here&amp;#39;s what the app looked like at one point of development:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/36O3QcwHFfcj7gFzUG5lTB/cf232e6beb87bd0f8cceba4ed33b83ce/Screen_recording_20240930_062846.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;Now that you have an idea of what I was building, let&amp;#39;s get into my thoughts about Compose Multiplatform. &lt;/p&gt;
&lt;h2 id=&quot;some-things-just-work&quot;&gt;Some Things Just Work&lt;/h2&gt;
&lt;p&gt;After I had created the first version of the app, there was one thing I was really surprised about: On the UI level, things just worked. &lt;/p&gt;
&lt;p&gt;One of these working things was the illustration. I first drafted the illustration for another project with Compose (as part of my explorations with Canvas) and wanted to port it to a Kotlin Multiplatform app. What did I do? I copy-pasted the code and built it, and it just worked on both Android and iOS. I don&amp;#39;t know why, but I expected some problems at that point. &lt;/p&gt;
&lt;p&gt;Other UI components I used at this point also just worked. Sure, I was building with the familiar elements, so everything looked like Android apps, even on iOS, but nothing crashed. I could run the application on both platforms. &lt;/p&gt;
&lt;p&gt;Once I ensured nothing crashed, I wanted to make the color picker modals more native-like for iOS. That leads to the next thing I realized - there aren&amp;#39;t that many resources available yet. &lt;/p&gt;
&lt;h2 id=&quot;lack-of-resources&quot;&gt;Lack of Resources&lt;/h2&gt;
&lt;p&gt;So, when I started investigating how to create the iOS-like color picker, I did not find that much material out there. Yes, there are the official docs (both for Compose Multiplatform and for iOS), but I feel like as someone who has never done iOS development, the bridge between these concepts is still missing. The technology is still so new and evolving, that there are a lot of  blank spots, and it can be hard to find any blog posts, videos, or any other resources to understand how to apply the basic concepts to action. &lt;/p&gt;
&lt;p&gt;And this is normal for new technologies - it takes time to build the community, and for developers to experiment and find these things, and start writing about more complex use cases. That&amp;#39;s something I&amp;#39;ve seen with Android accessibility topics as well.  &lt;/p&gt;
&lt;p&gt;In this particular case, if I had more experience with iOS development, I think I could apply that knowledge pretty straightforwardly. But I don&amp;#39;t, so it will take time, asking around from more knowledgeable people, and some trial and error. And once I get there, I will, naturally, create some of those resources I&amp;#39;m looking for now.&lt;/p&gt;
&lt;p&gt;Also, it&amp;#39;s worth noting that Compose Multiplatform for iOS is still in Beta, so it&amp;#39;s also natural that there aren&amp;#39;t too many resources around. &lt;/p&gt;
&lt;p&gt;However, not everything is lacking resources. Some things are documented well, and I really enjoyed learning about the expect / actual -pattern used to bridge the gap between native implementations and shared code. Let&amp;#39;s talk about that next. &lt;/p&gt;
&lt;h2 id=&quot;expect--actual-pattern-is-awesome&quot;&gt;Expect / Actual Pattern is Awesome&lt;/h2&gt;
&lt;p&gt;So, in Kotlin (and Compose) Multiplatform, shared code is written for all platforms, and platform-specific code can be written for individual platforms. It also has this pattern to write a function in the shared code, annotate it with &lt;code&gt;expect&lt;/code&gt;, and then create the actual implementations in native platform code to work in different environments. And I love it.&lt;/p&gt;
&lt;p&gt;I think these kinds of patterns make multiplatform development a compelling option when you can actually write the code for the platforms in native languages if needed. It also allows flexibility - if some shared implementation doesn&amp;#39;t work, or if you want to build apps to match the expected experiences on different platforms, it&amp;#39;s possible this way. &lt;/p&gt;
&lt;p&gt;I am comparing this experience with coding with React Native and using apps that have been coded with React Native, and I am excited. I keep telling everyone that switching to Android and Kotlin from JavaScript (and TypeScript) has been the one thing that has improved my life quality, and being able to build cross-platform apps with Kotlin is just awesome. &lt;/p&gt;
&lt;p&gt;In addition, with Kotlin and Compose Multiplatform, maybe the apps would not always look like iOS apps. For some reason, all the React Native apps I&amp;#39;ve used are more iOS-y than I&amp;#39;d like to see on my Android device. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So, all in all, my experiences with Compose Multiplatform (and Kotlin Multiplatform) have been positive so far. I recognize that the technology is still evolving, but I see a lot of potential for streamlining app development. As an Android developer, I really hope that it will get adopted more and more. I enjoy building apps with Kotlin. &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jetbrains.com/compose-multiplatform/&quot;&gt;Compose Multiplatform&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/multiplatform.html&quot;&gt;Kotlin Multiplatform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://neule.art/&quot;&gt;neule.art&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Not a Phase - Text with Compose and Canvas</title>
    <link href="https://eevis.codes/blog/2024-11-10/not-a-phase-text-with-compose-and-canvas/" />
    <updated>2024-11-10T13:10:55.040Z</updated>
    <id>https://eevis.codes/blog/2024-11-10/not-a-phase-text-with-compose-and-canvas/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/4dT1UJ63ish2RT0con2dhZ/4124343ca5aaa47f9f4270a9cfcd3c1f/not-a-phase-square.png"/>]]>
      &lt;p&gt;I&amp;#39;ve continued my journey with Compose and Canvas! After exploring drawing and animating shapes, I wanted to learn more about text. Bi-visibility Day was coming, so I drew a small animation to publish on Instagram. The final animation looks like this:&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/6wIDCFUsa9pvVru3XJXbmK/9d9480f5c9916840d321d139ce566ada/Screen_Recording_2024-09-21_at_10.52.20.mov&quot; controls=&quot;&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;In this blog post, we will look at how to add text to Canvas and position and animate it. We&amp;#39;re also utilizing custom Google Fonts in the drawing. &lt;/p&gt;
&lt;p&gt;If you&amp;#39;re interested in reading the first two posts, here are the links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-09-26/paint-the-stars-drawing-with-compose-and-canvas/&quot;&gt;Paint the Stars - Drawing with Compose and Canvas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-10-06/floating-in-space-animations-with-compose-and-canvas/&quot;&gt;Floating in Space - Animations with Compose and Canvas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;before-we-start&quot;&gt;Before We Start&lt;/h2&gt;
&lt;p&gt;Before we start drawing, I want to say a few words about the design. It has the moon in the waning crescent phase, with a dashed line to complete it to the full moon shape. The text says, &amp;quot;Not a phase&amp;quot;. &lt;/p&gt;
&lt;p&gt;Now, if you&amp;#39;re familiar with the discrimination and stereotypes bisexuals face, you probably already know what all of this means. But for those who are not, one of the stereotypes is that bisexuality is &amp;quot;just a phase on the way to being straight/gay&amp;quot;. &lt;/p&gt;
&lt;p&gt;But it&amp;#39;s not - it&amp;#39;s an (umbrella) term for people who feel attraction towards their own and other genders. And even if a bi person is in a monogamous relationship with a person from one gender, it doesn&amp;#39;t make them straight/gay. They&amp;#39;re still bi. &lt;/p&gt;
&lt;p&gt;So yeah, we&amp;#39;re here. We exist. &lt;/p&gt;
&lt;p&gt;Now, let&amp;#39;s get to the coding part.&lt;/p&gt;
&lt;h2 id=&quot;drawing-the-text&quot;&gt;Drawing the Text&lt;/h2&gt;
&lt;h3 id=&quot;measuring&quot;&gt;Measuring&lt;/h3&gt;
&lt;p&gt;Drawing text on Canvas is a two-step process: First, measure the text and then draw it. To start with measuring, we&amp;#39;ll need a &lt;code&gt;TextMeasurer&lt;/code&gt;, and with Compose-code, we have this neat remember-function we can use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val textMeasurer = rememberTextMeasurer()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For measuring, &lt;code&gt;TextMeasurer&lt;/code&gt; has a function &lt;code&gt;measure&lt;/code&gt;, which takes in the text as either &lt;code&gt;AnnotatedString&lt;/code&gt; or &lt;code&gt;String&lt;/code&gt;, and a bunch of other (mainly) optional parameters that affect the size of the text. Things like &lt;code&gt;density&lt;/code&gt;, &lt;code&gt;layoutDirection&lt;/code&gt;, &lt;code&gt;style&lt;/code&gt;, &lt;code&gt;fontFamilyResolver&lt;/code&gt;, and others. &lt;/p&gt;
&lt;p&gt;We will divide the text into two strings, as we want to animate and position them a bit differently. As both of our texts are just simple strings with one style, we can use the &lt;code&gt;String&lt;/code&gt;-version for both. The first version of the &amp;quot;Not&amp;quot;-text looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val notText =
    textMeasurer.measure(
        text = &amp;quot;Not&amp;quot;,
        style =
            MaterialTheme.typography.titleSmall.copy(
                brush = Brush.linearGradient(
                    colors = Colors.biFlag
                ),
            ),
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the &lt;code&gt;measure&lt;/code&gt;-function, we pass in the text and then styles. We want to use the theme typography here for straightforwardness, so we copy the small title styles and add a brush to have a linear gradient as the text color. Here, we&amp;#39;re using the bi-flag colors pink, purple, and blue. &lt;/p&gt;
&lt;p&gt;The second text is pretty similar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val phaseText =
    textMeasurer.measure(
        text = &amp;quot;a phase&amp;quot;,
        style =
            MaterialTheme.typography.titleLarge.copy(
                brush =
                    Brush.linearGradient(
                        colors = Colors.biFlag,
                    ),
                fontSize = 30.sp,
            ),
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this text, we&amp;#39;re utilizing the large title styles from the theme. In addition to gradient colors, we&amp;#39;re setting the font size to 30 &lt;code&gt;sp&lt;/code&gt; to make the text bigger. &lt;/p&gt;
&lt;p&gt;Alright, now we have everything we need from the measuring step. Next up is drawing the texts on canvas. &lt;/p&gt;
&lt;h3 id=&quot;drawing&quot;&gt;Drawing&lt;/h3&gt;
&lt;p&gt;Compose Canvas has a method called &lt;code&gt;drawText&lt;/code&gt; for drawing text. It takes in a &lt;code&gt;TextLayoutResult&lt;/code&gt;, which is the type that &lt;code&gt;measure&lt;/code&gt; function returns. In addition, it takes other parameters meant for styling and positioning the text on Canvas. &lt;/p&gt;
&lt;p&gt;For the &lt;code&gt;notText&lt;/code&gt; we defined in the previous subsection, the &lt;code&gt;drawText&lt;/code&gt; would look like this: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawText(
    textLayoutResult = notText,
    topLeft =
        Offset(
            size.width * 0.25f,
            size.height * 0.6f,
        ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We pass in the text layout result, and then we define the &lt;code&gt;topLeft&lt;/code&gt; offset to position the text correctly.&lt;/p&gt;
&lt;p&gt;The other text is a bit different. We want to position it relative to the &lt;code&gt;notText&lt;/code&gt;, so we use &lt;code&gt;notText&lt;/code&gt; for calculating the correct position:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawText(
    textLayoutResult = phaseText,
    topLeft =
        Offset(
            x = size.width * 0.35f,
            y = (size.height * 0.6f + notText.size.height * 0.7f),
        ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So here, we define the y-offset to be the same as for the &lt;code&gt;notText&lt;/code&gt;, and then we add 70% of the height of the &lt;code&gt;notText&lt;/code&gt;. This could be the whole height, but I wanted to keep less break between the texts. &lt;/p&gt;
&lt;p&gt;After these steps, our text looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/3MqNSjlfLXpPbpu3PhYTJH/55636dcd50acba4033d7f4a617b5cc21/not-a-phase-colors.png&quot; alt=&quot;Blue background, on which there is the moon in waning crescent phase in solid line and line to complete the circle for the moon. Under it, there&amp;#39;s the text &amp;#39;Not a Phase&amp;#39; with a pink, purple, and blue gradient.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There is just one thing left for the drawing - using custom fonts. Let&amp;#39;s talk about that next. &lt;/p&gt;
&lt;h3 id=&quot;adding-fonts&quot;&gt;Adding Fonts&lt;/h3&gt;
&lt;p&gt;For this animation, I wanted to have custom fonts. After playing around with Google Fonts, I decided that the two fonts I&amp;#39;m using are Poppins and Damion. &lt;/p&gt;
&lt;p&gt;Android documentation has a page about adding fonts to your project: &lt;a href=&quot;https://developer.android.com/develop/ui/compose/text/fonts#downloadable-fonts&quot;&gt;Work with fonts&lt;/a&gt;. However, I accidentally found that Android Studio lets you add Google Fonts as XML files straightforwardly. Here&amp;#39;s how it happens:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to Resource Manager and select the &amp;quot;Font&amp;quot;-tab.&lt;/li&gt;
&lt;li&gt;Click the &amp;quot;+&amp;quot; button to add new resource. &lt;/li&gt;
&lt;li&gt;Select &amp;quot;More Fonts...&amp;quot;.&lt;/li&gt;
&lt;li&gt;Find the Google Font you want to use, select weights, and press OK. &lt;/li&gt;
&lt;li&gt;Let Android Studio add everything needed, like the certification for fonts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;However, previews don&amp;#39;t work correctly if you do it this way and don&amp;#39;t import the ttf-files for fonts. So, if you rely on previews when developing, importing those files should resolve the issue. &lt;/p&gt;
&lt;p&gt;After the font is available, the next thing to do is to use it in the styles. Here&amp;#39;s the code for the font families we&amp;#39;re going to use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val PoppinsFontFamily =
    FontFamily(
        Font(R.font.poppins_bold, FontWeight.Bold),
    )

val DamionFontFamily =
    FontFamily(
        Font(R.font.damion, FontWeight.Normal),
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we add the font families to both texts - Damion for the &amp;quot;Not&amp;quot; text and Poppins to the &amp;quot;a phase&amp;quot;-text:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val notText =
    textMeasurer.measure(
        text = &amp;quot;Not&amp;quot;,
        style =
            MaterialTheme.typography.titleSmall.copy(
                ...
                fontFamily = DamionFontFamily
            ),
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val phaseText =
    textMeasurer.measure(
        text = &amp;quot;a phase&amp;quot;,
        style =
            MaterialTheme.typography.titleLarge.copy(
                ...
                fontFamily = PoppinsFontFamily
            ),
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After these changes, the drawing looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1W1AfAiML2UrCSZmgdq6kq/9bdc5df66b835b0316df0bba1f30b0ef/not-a-phase-fonts.png&quot; alt=&quot;The same layout as in the previous picture, but the not-text uses Damion-font and a Phase-text uses Poppins-font.&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;animating-the-text&quot;&gt;Animating the Text&lt;/h2&gt;
&lt;p&gt;The last step we&amp;#39;ll need to take is animating the text. We will do that by animating colors and floats. To set things up, let&amp;#39;s define &lt;code&gt;infiniteTransition&lt;/code&gt;, which we&amp;#39;re going to use later: &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val infiniteTransition = rememberInfiniteTransition(
    label = &amp;quot;infinite&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also want to show the color animation first on the &amp;quot;not&amp;quot;-text and only after that on the &amp;quot;a phase&amp;quot;-text. One way to accomplish that is to define a helper float, based on which we use to animate the words. We&amp;#39;ll get back to the implementation later. &lt;/p&gt;
&lt;p&gt;We&amp;#39;ll define a variable called &lt;code&gt;animationPosition&lt;/code&gt;, an infinitely transitioning float from 0f to 4f, which restarts from 0 when it reaches 4. These values could be anything, but after testing, I found that these values worked best when combined with other things in this drawing. &lt;/p&gt;
&lt;p&gt;The code for &lt;code&gt;animationPosition&lt;/code&gt; could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val animationPosition by infiniteTransition.animateFloat(
    initialValue = 0f,
    targetValue = 4f,
    animationSpec =
        infiniteRepeatable(
            tween(
                durationMillis = 10000,
                easing = EaseIn,
            ),
            RepeatMode.Restart,
        ),
    label = &amp;quot;animationPosition&amp;quot;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition, we will define a helper function for animating the colors. Let&amp;#39;s call it &lt;code&gt;biColorsAnimated&lt;/code&gt;, define it to take in a Boolean parameter &lt;code&gt;animated&lt;/code&gt;, and return a list of colors:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun biColorsAnimated(animated: Boolean): List&amp;lt;Color&amp;gt; {
    ....
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the function, we define our animated colors. We first create a list with the colors, and then map through it. For each color, we return &lt;code&gt;animateColorAsState&lt;/code&gt;&amp;#39;s value, which has the type &lt;code&gt;Color&lt;/code&gt;, and finally, we return the list of colors: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val colors = listOf(
    biFlag.pink,
    biFlag.purple,
    biFlag.blue
)

return colors.map {
    animateColorAsState(
        targetValue = if (animated) it else white,
        animationSpec =
            tween(
                durationMillis = 1000,
                easing = EaseInBounce,
            ),
        label = it.toString()
    ).value
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, we have the bi flag&amp;#39;s colors as animated values and can use them with our text.  &lt;/p&gt;
&lt;p&gt;Finally, we get to tie everything together. For both of the texts, we change the brush gradient&amp;#39;s color parameter to use this new function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val notText =
    textMeasurer.measure(
        text = &amp;quot;Not&amp;quot;,
        style =
            MaterialTheme.typography.titleSmall.copy(
                brush =
                    Brush.linearGradient(
                        colors = biColorsAnimated(
                            animated = animationPosition in 0.5f..1.5f
                        ),
                    ),
            ...
            ),
    )

val phaseText =
    textMeasurer.measure(
        text = &amp;quot;a phase&amp;quot;,
        style =
            MaterialTheme.typography.titleLarge.copy(
                brush =
                    Brush.linearGradient(
                        colors = biColorsAnimated(
                            animated = animationPosition in 2f..3.5f
                        ),
                    ),
            ...
            ),
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use the &lt;code&gt;animationPosition&lt;/code&gt; value to define if the colors for that text are animated. For the first text, we change the colors from white to the bi flag colors if the &lt;code&gt;animationPosition&lt;/code&gt; is between 0.5f and 1.5f, and for the second, if the value is between 2f and 3.5f. &lt;/p&gt;
&lt;p&gt;These changes get us the animation you can see at the beginning of this blog post. You can find &lt;a href=&quot;https://gist.github.com/eevajonnapanula/f47b5eab078cf903648555559ba50b2d&quot;&gt;the complete code in this code snippet&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked into adding text to Canvas, using custom Google Fonts, and animating colors. There was a lot to cover, but the end result is pretty nice!&lt;/p&gt;
&lt;p&gt;I hope you&amp;#39;ve enjoyed this blog post and learned something. If you want to share your learnings, post on the social media of your choosing, or let me know in the comments if you&amp;#39;re reading this on Dev or Medium. &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-09-26/paint-the-stars-drawing-with-compose-and-canvas/&quot;&gt;Paint the Stars - Drawing with Compose and Canvas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-10-06/floating-in-space-animations-with-compose-and-canvas/&quot;&gt;Floating in Space - Animations with Compose and Canvas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/develop/ui/compose/text/fonts#downloadable-fonts&quot;&gt;Work with fonts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/eevajonnapanula/f47b5eab078cf903648555559ba50b2d&quot;&gt;The complete code in this code snippet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Testing Different Navigation Options with Compose</title>
    <link href="https://eevis.codes/blog/2024-12-03/testing-different-navigation-options-with-compose/" />
    <updated>2024-12-04T03:34:59.673Z</updated>
    <id>https://eevis.codes/blog/2024-12-03/testing-different-navigation-options-with-compose/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1qdRux8Yq8yBXwWLdk1Wjh/95bef5e8132bbcf45def1e5f2c73bdfd/testing-navigation-square.png"/>]]>
      &lt;p&gt;One part of creating accessible Android apps is to provide alternative navigation options. Some examples include touch (or pointer) input, keyboard navigation, switch navigation, and screen reader navigation. But how can you write tests for these different ways of navigation? &lt;/p&gt;
&lt;p&gt;In this blog post, I&amp;#39;ll share some examples of how to do that. I&amp;#39;m using an old demo project about making graphs more accessible and demonstrating how to write tests for the different elements I&amp;#39;ve explained with that demo project. &lt;/p&gt;
&lt;h2 id=&quot;about-the-code-were-using&quot;&gt;About the Code We&amp;#39;re Using&lt;/h2&gt;
&lt;p&gt;As mentioned, I&amp;#39;m using an old demo project as the basis for the tests. In short, it contains a graph displaying data and is navigable with touch input, keyboard, switch device, and screen reader. The additional buttons for changing the highlighted sections in the chart also work for someone who has, for example, tremors in their hands or reduced dexterity. &lt;/p&gt;
&lt;p&gt;If you want to learn more about how I built the UI and the reasons behind the decisions, I&amp;#39;ve added links to all the blog posts in the &lt;a href=&quot;https://eevis.codes/blog/2024-12-03/testing-different-navigation-options-with-compose/#related-blog-posts&quot;&gt;Related Blog Posts&lt;/a&gt; section. &lt;/p&gt;
&lt;p&gt;Alright, let&amp;#39;s get to writing tests!&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-tests&quot;&gt;Setting Up The Tests&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s first set up the tests by creating a test class in the &lt;code&gt;androidTest&lt;/code&gt;-package, defining &lt;code&gt;composeTestRule&lt;/code&gt;, and adding a setup function that runs before each test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class GraphScreenTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Before
    fun setupTests() {
        composeTestRule.setContent {
            GraphExampleTheme {
                GraphScreen()
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another part of the setup phase is deciding how we will retrieve the elements we use for testing. In this case, I decided to use test tags for simplicity, and I&amp;#39;ve defined a &lt;code&gt;TestTags&lt;/code&gt;-object for sharing between the UI and tests. This solution is straightforward and might not be your choice in a production app, but as this is a demo, it uses the most explicit option. &lt;/p&gt;
&lt;p&gt;You can find &lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/e693b4ee915d9ba2b50be7b4783a9fa91c17aa62&quot;&gt;all the changes from this blog post in this commit&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;touch-navigation&quot;&gt;Touch Navigation&lt;/h2&gt;
&lt;p&gt;The first tests we&amp;#39;re going to write are about touch interaction. First, here&amp;#39;s a short video of how things work in the UI when a user uses their finger to drag over the chart: &lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/ZHIMzyIPuomkiPmCrlxYs/573cef254ce48f72e302d972a103a8a7/final-starting-point.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;So, when a user moves their pointer input around in the graph, a box with values appears in the bottom right corner of the UI, displaying the values for the selected year.&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s first get the elements we&amp;#39;re going to use in the test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Test
fun touchInteractionsWorkCorrectly() {
    val labels = 
        composeTestRule.onNode(hasTestTag(TestTags.labelsTestTag))
    val chart = 
        composeTestRule.onNode(hasTestTag(TestTags.chartTestTag))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why these two? First, the &lt;code&gt;labels&lt;/code&gt; variable is the one we&amp;#39;re using to check if things work correctly. It contains the information that changes, so by checking the year, we can ensure that navigation works correctly. Second, the chart is the one we&amp;#39;re interacting with.&lt;/p&gt;
&lt;p&gt;The actual tests look like this: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Test
fun touchInteractionsWorkCorrectly() {
    ...

    labels.assertIsNotDisplayed()

    // Navigate forward
    chart.performTouchInput {
        swipeRight(startX = 0f, endX = 30f)
    }

    labels.assertIsDisplayed()
    labels.onChildren().assertAny(hasText(&amp;quot;2015&amp;quot;))

    // Navigate forward
    chart.performTouchInput {
        swipeRight(startX = 30f, endX = 70f)
    }

    labels.onChildren().assertAny(hasText(&amp;quot;2016&amp;quot;))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We first assert that the labels are not visible because that&amp;#39;s how the UI is before navigation actions. After that, we perform touch input by swiping right, asserting that the correct year (in this case, &amp;quot;2015&amp;quot;) is displayed in the labels component. &lt;/p&gt;
&lt;p&gt;The numbers we use for &lt;code&gt;swipeRight&lt;/code&gt; are based on the code, and the 35-pixel swipe is still inside the area used in the code for deciding what year is shown. In the same way, the second swipe from 30 to 70 moves from the first year to the second year. &lt;/p&gt;
&lt;p&gt;Alright, we&amp;#39;ve written a test for touch/pointer input navigation. Next, we want to write tests for keyboard navigation. &lt;/p&gt;
&lt;h2 id=&quot;keyboard-navigation&quot;&gt;Keyboard Navigation&lt;/h2&gt;
&lt;p&gt;The following video demonstrates what the keyboard navigation looks like on the graph when a user presses the &amp;quot;next&amp;quot; button (right arrow in this case) to navigate forward in the graph: &lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/276754Rs7AbUSxMkUCYoyS/cd83be97e1d48708bff21209c756cc59/keyboard-improved-en.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;To test the keyboard navigation, we&amp;#39;ll need a similar setup as for the touch/pointer interactions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
@Test
fun keyboardNavigationWorksCorrectly() {
    val labels = 
        composeTestRule.onNode(hasTestTag(TestTags.labelsTestTag))
    val chart = 
        composeTestRule.onNode(hasTestTag(TestTags.chartTestTag))

    labels.assertIsNotDisplayed()

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this test, we define the same variables (&lt;code&gt;labels&lt;/code&gt; and &lt;code&gt;chart&lt;/code&gt;) and then assert that the labels component is not displayed. &lt;/p&gt;
&lt;p&gt;Next, we&amp;#39;ll need to perform some keyboard input actions. We can do that with &lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/ui/test/package-summary#(androidx.compose.ui.test.SemanticsNodeInteraction).performKeyInput(kotlin.Function1)&quot;&gt;&lt;code&gt;performKeyInput&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;pressKey&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;chart.performKeyInput {
    pressKey(Key.DirectionRight)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Key.DirectionRight&lt;/code&gt; is the key for the right-pointing arrow. For the test, we want to first navigate forward some years, assert that the position is correct, and then navigate back and assert the year:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Test
fun keyboardNavigationWorksCorrectly() {
   ...

    // Navigate forward
    chart.performKeyInput {
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
    }

    labels.onChildren().assertAny(hasText(&amp;quot;2020&amp;quot;))

    // Navigate back
    chart.performKeyInput {
        pressKey(Key.DirectionLeft)
        pressKey(Key.DirectionLeft)
        pressKey(Key.DirectionLeft)
    }

    labels.onChildren().assertAny(hasText(&amp;quot;2017&amp;quot;))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These lines assert that the keyboard navigation works correctly. The last thing to test in the context of this blog post is the on-screen button navigation. &lt;/p&gt;
&lt;h2 id=&quot;navigation-using-on-screen-buttons&quot;&gt;Navigation Using On-Screen Buttons&lt;/h2&gt;
&lt;p&gt;The previous videos didn&amp;#39;t display the on-screen buttons because they were recorded before adding them. Here&amp;#39;s a video with the buttons and how the navigation works:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/4XpsFYQJKccoUFDRh8C1a1/927cecd6256905816651b3cd0e7d2561/Screen_recording_20231208_072026.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;So, to test, again, we&amp;#39;ll have similar - but not exactly the same! - setup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Test
fun buttonNavigationWorksCorrectly() {
    val labels = 
        composeTestRule.onNode(hasTestTag(TestTags.labelsTestTag))
    val leftButton =
        composeTestRule.onNode(hasTestTag(TestTags.leftButtonTestTag))
    val rightButton = 
        composeTestRule.onNode(hasTestTag(TestTags.rightButtonTestTag))

    labels.assertIsNotDisplayed()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This time, besides getting the labels, we don&amp;#39;t need the chart component at all. Instead, we&amp;#39;ll get a reference to both buttons on the screen. &lt;/p&gt;
&lt;p&gt;Next, we want to navigate forward by clicking the button and asserting that the year on the label is correct. After that, we do some forward and backward navigation to ensure both buttons work correctly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Test
fun buttonNavigationWorksCorrectly() {
    ...

    // Navigate forward
    rightButton.performClick()

    labels.assertIsDisplayed()

    labels.onChildren().assertAny(hasText(&amp;quot;2015&amp;quot;))

    // Navigate forward
    rightButton.performClick()
    rightButton.performClick()
    rightButton.performClick()
    rightButton.performClick()

    // Navigate back
    leftButton.performClick()
    leftButton.performClick()

    labels.onChildren().assertAny(hasText(&amp;quot;2017&amp;quot;))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this is how we can test the on-screen button navigation in the graph. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve written tests for three different navigation styles: Touch/pointer input, keyboard navigation, and on-screen button navigation. This way, we&amp;#39;ve tested that users using different navigation methods and tools can use the app. &lt;/p&gt;
&lt;p&gt;Do you test for these interactions and navigation alternatives? If so, do you have any tips on testing them?&lt;/p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;/h2&gt;
&lt;h3 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/graph-accessibility-example/commit/e693b4ee915d9ba2b50be7b4783a9fa91c17aa62&quot;&gt;All the changes from this blog post in this commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/ui/test/package-summary#(androidx.compose.ui.test.SemanticsNodeInteraction).performKeyInput(kotlin.Function1)&quot;&gt;&lt;code&gt;performKeyInput&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;related-blog-posts&quot;&gt;Related Blog Posts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-06-03/accessibility-tests-in-compose-name-role-value/&quot;&gt;Accessibility Tests in Compose - Name, Role, Value&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-24/more-accessible-graphs-with-jetpack-compose-part-1-adding-content/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-07-31/more-accessible-graphs-with-jetpack-compose-part-2-adding-keyboard-interaction/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 2: Adding Keyboard Interaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-08-16/more-accessible-graphs-with-jetpack-compose-part-3-differentiating-without/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 3: Differentiating without Color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-12-09/more-accessible-graphs-with-jetpack-compose-part-4-on-screen-control-buttons/&quot;&gt;More Accessible Graphs with Jetpack Compose Part 4: On-Screen Control Buttons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Support Time to Take Action with Compose</title>
    <link href="https://eevis.codes/blog/2024-12-28/support-time-to-take-action-with-compose/" />
    <updated>2024-12-28T09:31:16.891Z</updated>
    <id>https://eevis.codes/blog/2024-12-28/support-time-to-take-action-with-compose/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3tbiA9o8J2eF37zhyWV7FJ/f0f23423a5309d024d2e1dd0ffa3641f/time-to-take-action-square.png"/>]]>
      &lt;p&gt;While working on my master&amp;#39;s thesis and the &lt;a href=&quot;https://android-a11y-checks.netlify.app/checks&quot;&gt;Android Accessibility Checklist&lt;/a&gt;, one of the answers to my survey mentioned an accessibility setting called time to take action. Since then, I&amp;#39;ve been curious about it and wanted to write a blog post as I feel it&amp;#39;s one of the undersupported accessibility settings and really useful for those who utilize it. &lt;/p&gt;
&lt;p&gt;So, what is time to take action, and why would someone use it? And why should we, as developers, care? Let&amp;#39;s talk about that first.&lt;/p&gt;
&lt;h2 id=&quot;time-to-take-action&quot;&gt;Time to Take Action&lt;/h2&gt;
&lt;p&gt;Time to take action is a setting that controls the minimum time in which temporary messages asking a user to take action are shown. So, for example, when you press the volume buttons on your Android phone, you should see the volume controls next to the volume buttons, and this setting controls how long they&amp;#39;re visible. &lt;/p&gt;
&lt;p&gt;This setting has different options: Default, 10 seconds, 30 seconds, 1 minute and 2 minutes. You can find them from Settings -&amp;gt; Accessibility Settings -&amp;gt; Timing controls -&amp;gt; Time to take action (Accessibility timeout). The screen looks something like this, depending on your phone&amp;#39;s version and manufacturer:&lt;/p&gt;
&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2Ddu45szS5of65d6Aoz0w8/f9eb9a67633858c83595f987acb5e355/Screenshot_20241227_064718.png&quot; alt=&quot;Time to take action-settings screen from Pixel phone with an illustration and the following options visible: Default (selected), 10 seconds, 30 seconds.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;p&gt;And why would someone need this setting? Well, the source code and documentation for a method we&amp;#39;re going to use in a moment puts it pretty well:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some users may need extra time to review the controls, or to reach them, or to activate assistive technology to activate the controls automatically.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Okay, now we&amp;#39;ve established what the time to take action is. However, the question of relevance remains: Why should we care as developers? Isn&amp;#39;t it something that the operating system handles automatically?&lt;/p&gt;
&lt;p&gt;The answer is yes and no. Most components on the operating system level and Material3 components respect this setting. However, when you&amp;#39;re building a custom component, it&amp;#39;s not automatically respected, and you&amp;#39;ll need to add support for it. That&amp;#39;s why the settings page mentions that not all apps support the setting. The thing is, developers usually don&amp;#39;t know about it. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s next talk about how to support time to take action-setting with a custom component. &lt;/p&gt;
&lt;h2 id=&quot;how-to-respect-time-to-take-action&quot;&gt;How to Respect Time to Take Action?&lt;/h2&gt;
&lt;p&gt;For the sake of example, we&amp;#39;ll build a small tooltip component with a button to close it. The tooltip disappears after the minimum time to take action if the user does nothing. The default value, in our case, is set to 10 seconds. &lt;/p&gt;
&lt;p&gt;Before diving into code, here&amp;#39;s a video where the tooltip is first shown with the time to take action setting with a default value, and then it&amp;#39;s set to 30 seconds:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/7eCAPk1YN8wpjfcPeEkkj6/5ff462d3ef7abbaddc0e4b9b7798f84c/Screen_recording_20241227_110719.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;This example doesn&amp;#39;t contain all the code (like the tooltip content), just the relevant parts. As seen in the video, the tooltip becomes visible after the user clicks an &amp;quot;Info&amp;quot;-button, then is visible for the required time, depending on the time to take action-setting, and finally, disappears. The tooltip contains a button to hide the tooltip faster. &lt;/p&gt;
&lt;p&gt;We&amp;#39;re adding the support for time to take action in two steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Calculate the delay for hiding the tooltip&lt;/li&gt;
&lt;li&gt;Hide the tooltip after the calculated time has passed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first step is to calculate the delay, or &lt;code&gt;tooltipTimeout&lt;/code&gt;, as we&amp;#39;re calling it in the code: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun Tooltip(
    tooltipVisible: Boolean,
    hideTooltip: () -&amp;gt; Unit,
    modifier: Modifier = Modifier
) {
    val tooltipTimeout = LocalAccessibilityManager.current?
        .calculateRecommendedTimeoutMillis(
             originalTimeoutMillis = TOOLTIP_DEFAULT_TIMEOUT_MS,
             containsText = true,
             containsControls = true,
         ) ?: TOOLTIP_DEFAULT_TIMEOUT_MS

    ...

    TooltipContent(...)
}

// 10 seconds
const val TOOLTIP_DEFAULT_TIMEOUT_MS = 10000 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;LocalAccessibilityManager&lt;/code&gt; composition local provides a neat function for calculating the timeout, so we use that. 
The first parameter for &lt;code&gt;calculate&amp;shy;Recommended&amp;shy;Timeout&amp;shy;Millis&lt;/code&gt; is the original timeout milliseconds, which applies when there are no accessibility timeout needs. The other parameters are a bunch of boolean values informing about the element&amp;#39;s contents for which the timeout value is being calculated. These are about whether the element contains text, controls, or icons. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;calculate&amp;shy;Recommended&amp;shy;Timeout&amp;shy;Millis&lt;/code&gt; returns the recommended timeout time as &lt;code&gt;Long&lt;/code&gt;. As the &lt;code&gt;AccessibilityManager&lt;/code&gt; might not be available in some operating systems or OS versions and thus is nullable, we also need a default value. This value is the &lt;code&gt;TOOLTIP_DEFAULT_TIMEOUT_MS&lt;/code&gt; in our code - the same one we use for the original timeout. In the example, the value is 10 seconds.&lt;/p&gt;
&lt;p&gt;Next, we&amp;#39;ll use the freshly calculated timeout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun Tooltip(...) {

   ... 

    LaunchedEffect(tooltipVisible) {
        if (tooltipVisible) {
            delay(tooltipTimeout)
            hideTooltip()
        }
    }

    TooltipContent(...)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, what happens in the code snippet is that whenever the &lt;code&gt;tooltipVisible&lt;/code&gt;-value changes, we check if the change was for making the tooltip visible, and if so, then we first delay for the number of milliseconds we calculated in the previous step and then hide the tooltip. &lt;/p&gt;
&lt;p&gt;This way, we can respect the user&amp;#39;s settings for elements that they might need more time to interact with. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve discussed the time to take action setting, what it is, why developers should know about it, and how to respect it. We&amp;#39;ve done this by building a tooltip component, which hides after a certain amount of time. &lt;/p&gt;
&lt;p&gt;Did you know about this setting? Have you ever implemented support for it? If so, what kind of UI components were they?&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://android-a11y-checks.netlify.app/checks&quot;&gt;Android Accessibility Checklist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Year in Review - 2024 Edition</title>
    <link href="https://eevis.codes/blog/2024-12-31/year-in-review-2024-edition/" />
    <updated>2024-12-31T09:23:47.195Z</updated>
    <id>https://eevis.codes/blog/2024-12-31/year-in-review-2024-edition/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3j8TqaWlaeSWuJAFu0QiUt/d4e23b9975ae8fc74925508da4909027/yir-2024-square.png"/>]]>
      &lt;p&gt;It&amp;#39;s time for some yearly reflections again. What a year 2024 has been! Both in good and in bad. This yearly review is my fourth, and you can find the previous ones from these links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/?utm_source=yir-2024&quot;&gt;Year in Review - 2021 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-01-07/year-in-review-2022-edition/?utm_source=yir-2024&quot;&gt;Year in Review - 2022 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-12-29/year-in-review-2023-edition/?utm_source=yir-2024&quot;&gt;Year in Review - 2023 Edition&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Okay, let&amp;#39;s dive in.&lt;/p&gt;
&lt;h2 id=&quot;accomplishments&quot;&gt;Accomplishments&lt;/h2&gt;
&lt;p&gt;A lot has happened, and looking back, I&amp;#39;ve accomplished a bunch. Here are some of the highlights from the past year.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/74r3qpISCHgZR8sDIp6uJk/d7c04225c9ac5d46c673135d0b75d006/Screenshot_20240526-104425.png&quot; alt=&quot;Hi Eeva-Jonna, we are pleased to warmly welcome you to the Google Developer Experts Program! You are now officially a GDE!&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I become a GDE&lt;/strong&gt; (Google Developer Expert). That was a tremendous thing for me, getting recognition for my community contributions over the years. Also, after &lt;a href=&quot;https://eevis.codes/blog/2024-06-26/the-worst-case-of-imposter-syndrome/?utm_source=yir-2024&quot;&gt;a huge case of imposter syndrome&lt;/a&gt; I had in the spring, getting the email welcoming me to the GDE program felt just incredible. &lt;/p&gt;
&lt;p&gt;Other accomplishments from 2024 relate to my studies - first, in the spring, I &lt;strong&gt;completed my master&amp;#39;s thesis about Android developers and accessibility&lt;/strong&gt;. It was a fun project, and I&amp;#39;ve written about the checklist I developed during the process in this blog post: &lt;a href=&quot;https://eevis.codes/blog/2024-08-07/android-accessibility-checklist/?utm_source=yir-2024&quot;&gt;Android Accessibility Checklist&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I  also graduated in December&lt;/strong&gt;, and can now proudly say that I&amp;#39;m, in addition to my previous master&amp;#39;s degree in Russian language and culture, Master of Science in Economics and Business Administration, with a major in Information Systems (and the master&amp;#39;s program was technical communication).&lt;/p&gt;
&lt;p&gt;The final accomplishment I want to highlight is that &lt;strong&gt;I released &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.eevajonna.neuleart&quot;&gt;Neule.art Android app&lt;/a&gt;&lt;/strong&gt; in December. It&amp;#39;s a Compose multiplatform app, and I&amp;#39;m learning as I go - but it&amp;#39;s been super fun! And sometimes frustrating because I&amp;#39;m a total iOS noobie, and when I need to add something in native code, I sometimes don&amp;#39;t even know the right words to search for answers. Nevertheless, I&amp;#39;ve learned a ton in the process. I&amp;#39;ve written about the initial thoughts in this blog post: &lt;a href=&quot;https://eevis.codes/blog/2024-10-17/first-impressions-of-compose-multiplatform/?utm_source=yir-2024&quot;&gt;First Impressions of Compose Multiplatform&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;speaking&quot;&gt;Speaking&lt;/h2&gt;
&lt;p&gt;In 2024, I did some public speaking. I also had to cancel some things due to booking flights on the wrong days and realizing it only right when I was supposed to leave, and getting sick, which was a pity. But anyway, here are all the conferences and meetups I spoke at in 2024:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Conferences and Webinars&lt;ul&gt;
&lt;li&gt;Android Worldwide (online)&lt;/li&gt;
&lt;li&gt;Accessibility in mobile apps - IAAP Nordic (online)&lt;/li&gt;
&lt;li&gt;Droidcon Berlin&lt;/li&gt;
&lt;li&gt;Quality Forge (online)&lt;/li&gt;
&lt;li&gt;Droidcon London&lt;/li&gt;
&lt;li&gt;We Are Developers Live - Accessibility Day (online)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Meetups&lt;ul&gt;
&lt;li&gt;Turku &amp;lt;3 Frontend&lt;/li&gt;
&lt;li&gt;Turku Mobile&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was about to write that I didn&amp;#39;t do that much public speaking this year, but looking at the list and comparing it to last year, I actually spoke in more events than in 2023. Oh wow. That&amp;#39;s nice. &lt;/p&gt;
&lt;h2 id=&quot;writing&quot;&gt;Writing&lt;/h2&gt;
&lt;p&gt;In 2024, I improved my writing game. I wrote more, and I feel like the quality of my posts has increased. In total, I&amp;#39;ve published 27 blog posts (this being the 28th), and six of them got boosted by the Medium team. The topics of the blog posts were related to Android, Android accessibility, career and inclusion, and burnout. &lt;/p&gt;
&lt;p&gt;Also, most of my blog posts were featured in multiple newsletters, which always feels great. So, a big thanks to everyone behind those newsletters! Some of these newsletters include &lt;a href=&quot;https://androidweekly.net/&quot;&gt;Android Weekly&lt;/a&gt;, &lt;a href=&quot;https://jetc.dev/&quot;&gt;Jetpack Compose Newsletter&lt;/a&gt;, &lt;a href=&quot;https://softwaretestingnotes.substack.com/&quot;&gt;Software Testing Notes&lt;/a&gt;, and &lt;a href=&quot;https://softwaretestingweekly.com/&quot;&gt;Software Testing Weekly&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;I&amp;#39;ll also list some of my personal favorites from 2024:&lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Another Year Of Being in Gender Minority in Tech&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2024-03-01&quot;&gt;March 1st&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2024-03-01/another-year-of-being-in-gender-minority-in-tech/?utm_source=yir-2024&quot;&gt;Read the post Another Year Of Being in Gender Minority in Tech&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;This blog post is part of Dev.to&amp;#39;s #WeCoded campaign, which started as She coded, sharing stories from women in tech around International Women&amp;#39;s Day, and has evolved to a month-long celebration of gender minorities in tech. It was hard to write, and at the same time, I knew it had to be written. Normally, I would share the link to the original post in Dev, but to be honest,  the comments I&amp;#39;ve received there (and on Medium) are why I wanted to share this piece from my website, without comments.&lt;/p&gt;
&lt;p&gt;So yeah, in the post, I share my experiences of being in gender minority in tech from the previous year (at the time of writing), and some of them are super misogynist. With this post I also got my first experience of being harassed on other platforms by someone who was blocked on Dev. But more about that in next March&amp;#39;s #WeCoded-post.&lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Stacked Cards Layout With Compose - And Cats&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2024-07-11&quot;&gt;July 11th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2024-07-11/stacked-cards-layout-with-compose-and-cats/?utm_source=yir-2024&quot;&gt;Read the post Stacked Cards Layout With Compose - And Cats&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;I loved writing this blog post - it has two elements I really like: cats and custom layouts. In the blog post, I explain how to create a custom stacked card layout to display cat pictures in those cards. As often happens, this blog post was a byproduct of another blog post (which I haven&amp;#39;t yet published), and I had so much fun writing it. &lt;/p&gt;
&lt;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Not a Phase - Text with Compose and Canvas&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2023-11-10&quot;&gt;November 10th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2024-11-10/not-a-phase-text-with-compose-and-canvas/?utm_source=yir-2024&quot;&gt;Read the post Not a Phase - Text with Compose and Canvas&lt;/a&gt;
&lt;/article&gt;

&lt;p&gt;In the second half of 2024, I&amp;#39;ve been writing about Compose and Canvas, and this blog post is one in a series of those blog posts. This one discusses adding text and different text styles and fonts to Canvas. In addition, it highlights a theme dear to me: Bisexual visibility and existence. &lt;/p&gt;
&lt;h2 id=&quot;other-things&quot;&gt;Other Things&lt;/h2&gt;
&lt;p&gt;This year was a hard one. I mean, look at the world burning - that alone makes everything hard. But I also have been burned out pretty much the whole year - that seems to be my state of existence now. It got better in the summer and beginning of fall, until everything started falling apart in ways I never would have imagined them to do. &lt;/p&gt;
&lt;p&gt;But at the same time, 2024 was a year of me finding myself. It was the year when I&amp;#39;ve finally been able to do some things and live as myself. You see, I spent my youth and early adulthood in charismatic Christian circles, and only now, almost 10 years after leaving (or being forced out, because divorce is one of the biggest sins you can commit), I can finally come out as a nonbinary woman. Bisexuality was somehow an easier thing; I don&amp;#39;t know why.&lt;/p&gt;
&lt;p&gt;Also, I&amp;#39;ve finally done something I had planned for more than 10 years: I&amp;#39;ve got tattoos! They&amp;#39;re adorable and so freaking cool. And they won&amp;#39;t be the last ones. I&amp;#39;m so glad I work in tech and can afford such things. &lt;/p&gt;
&lt;p&gt;So, 2024 was not all bad. How about 2025?&lt;/p&gt;
&lt;h2 id=&quot;what-about-2025&quot;&gt;What About 2025?&lt;/h2&gt;
&lt;p&gt;Yeah, I&amp;#39;m not going to promise anything. But I have one wish - I wish that a year from now, I wouldn&amp;#39;t have to think or write about burnout so much. So, if you have a job opportunity where burnout is not that likely, I&amp;#39;m all ears. But I do want to do Android development and/or accessibility-related things.&lt;/p&gt;
&lt;p&gt;I wish you a better year in 2025! &lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/?utm_source=yir-2024&quot;&gt;Year in Review - 2021 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-01-07/year-in-review-2022-edition/?utm_source=yir-2024&quot;&gt;Year in Review - 2022 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-12-29/year-in-review-2023-edition/?utm_source=yir-2024&quot;&gt;Year in Review - 2023 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-06-26/the-worst-case-of-imposter-syndrome/?utm_source=yir-2024&quot;&gt;a huge case of imposter syndrome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-08-07/android-accessibility-checklist/?utm_source=yir-2024&quot;&gt;Android Accessibility Checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.eevajonna.neuleart&quot;&gt;Neule.art Android app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-10-17/first-impressions-of-compose-multiplatform/?utm_source=yir-2024&quot;&gt;First Impressions of Compose Multiplatform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://androidweekly.net/&quot;&gt;Android Weekly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jetc.dev/&quot;&gt;Jetpack Compose Newsletter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://softwaretestingnotes.substack.com/&quot;&gt;Software -Testing Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://softwaretestingweekly.com/&quot;&gt;Software Testing Weekly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-03-01/another-year-of-being-in-gender-minority-in-tech/?utm_source=yir-2024&quot;&gt;Read the post Another Year Of Being in Gender Minority in Tech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-07-11/stacked-cards-layout-with-compose-and-cats/?utm_source=yir-2024&quot;&gt;Read the post Stacked Cards Layout With Compose - And Cats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-11-10/not-a-phase-text-with-compose-and-canvas/?utm_source=yir-2024&quot;&gt;Read the post Not a Phase - Text with Compose and Canvas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>2025, Please, Be Gentle</title>
    <link href="https://eevis.codes/blog/2025-01-06/2025-please-be-gentle/" />
    <updated>2025-01-06T11:00:18.723Z</updated>
    <id>https://eevis.codes/blog/2025-01-06/2025-please-be-gentle/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/6CUCbyC9aXWrGYO2vUvDqa/94357069fb013d07b6676af08f07f42f/2025-bingo-square__1_.png"/>]]>
      &lt;p&gt;&lt;em&gt;This was originally written for Dev.to&amp;#39;s &lt;a href=&quot;https://dev.to/challenges/newyear&quot;&gt;New Year writing challenge.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;2025 has started, and while I&amp;#39;m actually skeptical of anything good happening (just look at the world burning), I decided to give some thought to what I hope to accomplish this year. You know, the good things, as an ask for the universe.&lt;/p&gt;
&lt;p&gt;I could describe this all as a roadmap for 2025. But I&amp;#39;ve learned over the years that my plans usually don&amp;#39;t come true, and something unexpected happens. So, instead of compiling a complete roadmap, I call these things wishes.&lt;/p&gt;
&lt;p&gt;I assembled them in the form of a bingo card. You know, the game where you mark something when it happens (or, in the original thing, when the bingo host announces the number in the card). And when the things you&amp;#39;ve marked create either in horizontal, vertical, or diagonal lines, you yell &amp;quot;Bingo!&amp;quot; and win something.&lt;/p&gt;
&lt;p&gt;I really wish I&amp;#39;d get a &amp;quot;Bingo&amp;quot; this year. I would really need it after the past *mumbles a number* years. Like, really, really need it. Of course, I&amp;#39;m putting in the work towards all of that stuff - but with many things, it&amp;#39;s not only about me. &lt;/p&gt;
&lt;p&gt;However, I wouldn&amp;#39;t be me if I wasn&amp;#39;t realistic. So, I also compiled another list of events I predict to happen this year. Heck, I expect that most of them will happen before March, and I will get back to them in my #WeCoded post. Unfortunately, coming from a minority position, I need to be prepared for a lot of negative stuff. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s start with the good stuff.&lt;/p&gt;
&lt;h2 id=&quot;2025-please-bring-some-goodness&quot;&gt;2025, Please, Bring Some Goodness&lt;/h2&gt;
&lt;p&gt;So, here are my wishes for 2025:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rzhvtkmvinr7p18rut90.png&quot; alt=&quot;Bingo card with my wishes for 2025.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The same bingo as a text version for better readability:&lt;/p&gt;
&lt;div class=&quot;table-container&quot; tabIndex=&quot;0&quot;&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;Learn a new skill&lt;/td&gt;
&lt;td&gt;More non-binary representation in conferences&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&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;Blog post gets boosted or selected to Dev&#39;s top 7&lt;/td&gt;
 &lt;td&gt;#WeCoded without jerks commenting&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Conference swag socks in my size&lt;/td&gt;
 &lt;td&gt;Someone says my work inspired them&lt;/td&gt;
 &lt;td&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;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;I think they&amp;#39;re pretty self-explanatory. In general, some are about work stuff, some are about non-work-but-tech stuff, and the rest are just me hoping for some cool things, like a night in nature or a new tattoo. &lt;/p&gt;
&lt;p&gt;But they&amp;#39;d make 2025 a pretty decent year. Especially if I don&amp;#39;t burn out. Which is an excellent segue to things I wish wouldn&amp;#39;t happen, but I predict to happen in 2025. &lt;/p&gt;
&lt;h2 id=&quot;but-i-must-be-realistic&quot;&gt;But I Must Be Realistic&lt;/h2&gt;
&lt;p&gt;Here&amp;#39;s a more realistic bingo card (without the good things):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4sf3h0ofssjoa7zw9shl.png&quot; alt=&quot;Bingo card with more realistic content.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And the text version of the card: &lt;/p&gt;
&lt;div class=&quot;table-container&quot; tabIndex=&quot;0&quot;&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;Being addressed as a guy&lt;/td&gt;
 &lt;td&gt;Someone gaslights me&lt;/td&gt;
 &lt;td&gt;Biased AI-generated content&lt;/td&gt;
 &lt;td&gt;Man speaks over me&lt;/td&gt;
 &lt;td&gt;Called &quot;intimidating&quot; for being assertive.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Someone interrupts when I&#39;m presenting&lt;/td&gt;
 &lt;td&gt;&quot;Gender doesn&#39;t matter, just talent&quot;&lt;/td&gt;
 &lt;td&gt;Man takes credit for what I said&lt;/td&gt;
 &lt;td&gt;Being left out of an important meeting&lt;/td&gt;
 &lt;td&gt;&quot;You&#39;d be heard better, if you weren&#39;t so angry&quot;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Someone assumes I can&#39;t handle my tech setup&lt;/td&gt;
 &lt;td&gt;Meeting a new person who assumes I&#39;m not a developer&lt;/td&gt;
 &lt;td&gt;&quot;I&#39;ve never seen anyone being discriminated&quot;&lt;/td&gt;
 &lt;td&gt;&quot;Intent over impact&quot;&lt;/td&gt;
 &lt;td&gt;Man needs to repeat what I&#39;ve said&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Invalidation of lived experience&lt;/td&gt;
 &lt;td&gt;&quot;Women just don&#39;t want to code&quot;&lt;/td&gt;
 &lt;td&gt;Someone else credited for my work&lt;/td&gt;
 &lt;td&gt;&quot;But they clearly didn&#39;t mean it&quot; &lt;/td&gt;
 &lt;td&gt;Sexist comment in a blog post or talk&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&quot;You were hired just for diversity quotas&quot;&lt;/td&gt;
 &lt;td&gt;Everyone&#39;s surprised after something I warned happens&lt;/td&gt;
 &lt;td&gt;Man splaining my expertise to me&lt;/td&gt;
 &lt;td&gt;&quot;Equality has gone too far&quot;&lt;/td&gt;
 &lt;td&gt;Hypothetical developer is referred to as &quot;he&quot; or other gendered noun&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;I&amp;#39;m exhausted after compiling this list. I wish I could say I had to look for examples elsewhere, but unfortunately, I&amp;#39;ve seen, heard, or experienced all of these during my career in tech. &lt;/p&gt;
&lt;p&gt;I hope I won&amp;#39;t get a bingo with this card. Unfortunately, that&amp;#39;s more likely than with the first one - or, at least, faster than with the first one. &lt;/p&gt;
&lt;p&gt;But I want to end on a positive note. I&amp;#39;m concentrating on the good, my well-being, and making the wishes I described above true. I&amp;#39;m manifesting a good year - a year with less discrimination, bias, and other similar things happening. &lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Using SVGs on Canvas with Compose Multiplatform</title>
    <link href="https://eevis.codes/blog/2025-01-15/using-svgs-on-canvas-with-compose-multiplatform/" />
    <updated>2025-01-15T06:00:02.402Z</updated>
    <id>https://eevis.codes/blog/2025-01-15/using-svgs-on-canvas-with-compose-multiplatform/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/4sULltFndqwaeMXauoe0gA/0fa943ae6afa977601e5f2a10c60e5c9/svgs-compose-multiplatform-square.png"/>]]>
      &lt;p&gt;One thing that has continued to amaze me with building my Compose Multiplatform app is how easily everything has worked with Canvas. When I started building &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.eevajonna.neuleart&quot;&gt;Neule.art&lt;/a&gt;, I assumed that Canvas would cause some problems, but it has worked smoothly on both Android and iOS. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve been writing posts about creative coding on Canvas (see, for example, &lt;a href=&quot;https://eevis.codes/blog/2024-11-10/not-a-phase-text-with-compose-and-canvas/&quot;&gt;Not a Phase - Text with Compose and Canvas&lt;/a&gt;), and also about my &lt;a href=&quot;https://eevis.codes/blog/2024-10-17/first-impressions-of-compose-multiplatform/&quot;&gt;First Impressions of Compose Multiplatform&lt;/a&gt;, and this post combines elements from both themes. &lt;/p&gt;
&lt;p&gt;There are different ways to parse an SVG to be used with Compose, and in this blog post, I&amp;#39;m looking into using path data. This approach requires some manual work, but it also allows better flexibility for controlling, e.g., colors of the individual elements within the SVG. &lt;/p&gt;
&lt;h2 id=&quot;what-were-building&quot;&gt;What We&amp;#39;re Building&lt;/h2&gt;
&lt;p&gt;Even though I&amp;#39;d love to show how I&amp;#39;ve built the shirt I&amp;#39;m using in Neule.art, simplifying the process into the form of a blog post is too difficult a task. I decided to create a smaller SVG, which we&amp;#39;re going to convert into Canvas. It looks like this: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2sIu7ybyAF7qOtyhQakk8M/87ff664ddb0ad2e014ea55b91fee7178/Group_10.png&quot; alt=&quot;Eighth hand drawn hearts in yellow, white, purple and black. &quot; /&gt;&lt;/p&gt;
&lt;p&gt;The hearts I&amp;#39;m using in the SVG are from &lt;a href=&quot;https://www.figma.com/community/file/1099013061143326702&quot;&gt;Sarah Laroche&amp;#39;s Vector Heart Figma resource&lt;/a&gt;. And if you&amp;#39;ve ever seen the colors anywhere, you might recognize them as being from the non-binary flag. &lt;/p&gt;
&lt;h2 id=&quot;getting-the-paths-from-an-svg&quot;&gt;Getting the Paths from an SVG&lt;/h2&gt;
&lt;p&gt;We first need something from the original SVG to draw it on Canvas: the paths of individual components within the SVG. &lt;/p&gt;
&lt;p&gt;In an SVG, the path&amp;#39;s &lt;code&gt;d&lt;/code&gt;-attribute contains the commands for defining the shape being drawn, and we&amp;#39;re using that to parse the SVG to the path on Canvas. If you&amp;#39;re unfamiliar with SVGs, I recommend checking &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG&quot;&gt;MDN&amp;#39;s documentation on SVGs&lt;/a&gt;, as it contains much in-depth information about SVGs.&lt;/p&gt;
&lt;p&gt;So, to prepare for drawing paths on Canvas, we&amp;#39;ll need an SVG image as code. Then, we need to copy the path&amp;#39;s &lt;code&gt;d&lt;/code&gt;-attribute&amp;#39;s content and finally store them somewhere inside our code. There are several options for opening the SVG&amp;#39;s code - for example, open the file in an IDE or inspect it in the browser&amp;#39;s developer tools. &lt;/p&gt;
&lt;p&gt;In our example, we store the path strings in a list with a variable called &lt;code&gt;pathStrings&lt;/code&gt;. We also want to attach the color information to each path to make the drawing phase easier, so we store the path string together with the color it will be drawn with. &lt;/p&gt;
&lt;p&gt;The following example lists only two of the hearts&amp;#39; paths, as the complete list would take up too much space. You can find the complete list of paths in the snippet linked at the end of the blog post. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val pathStrings = listOf(
    Pair(&amp;quot;M185.52 80.2313C172.773 67.8803 159.24 53.9097 154.36 36.3463C153.373 32.8076 152.633 29.1043 153.193 25.4737C154.813 14.8697 163.753 16.4547 168.74 23.722C175.593 33.7001 180.946 44.7021 184.58 56.2492C186.3 43.9382 188.04 31.5685 191.666 19.6794C192.786 16.0068 195.16 11.6456 198.98 12.0229C201.993 12.3195 203.8 15.5202 204.526 18.453C205.733 23.2988 205.426 28.3831 204.926 33.3509C202.806 52.8032 201.753 71.7397 198.673 91.1093C193.953 87.8965 189.62 84.204 185.52 80.2313&amp;quot;, Colors.white),
    Pair(&amp;quot;M32.5198 119.231C19.7732 106.88 6.2398 92.9097 1.3598 75.3463C0.373129 71.8076 -0.366871 68.1043 0.193129 64.4737C1.81313 53.8697 10.7531 55.4547 15.7398 62.722C22.5931 72.7001 27.9465 83.7021 31.5798 95.2492C33.2998 82.9382 35.0398 70.5685 38.6664 58.6794C39.7864 55.0068 42.1598 50.6456 45.9798 51.0229C48.9931 51.3195 50.7998 54.5202 51.5265 57.453C52.7331 62.2988 52.4265 67.3831 51.9265 72.3509C49.8065 91.8032 48.7532 110.74 45.6732 130.109C40.9532 126.897 36.6198 123.204 32.5198 119.231&amp;quot;, Colors.black),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have the path string and the color, we can move on to parsing the path strings into Compose&amp;#39;s &lt;code&gt;Path&lt;/code&gt; objects.&lt;/p&gt;
&lt;h2 id=&quot;parsing-paths&quot;&gt;Parsing Paths&lt;/h2&gt;
&lt;p&gt;Compose has this great thing called &lt;code&gt;PathParser&lt;/code&gt;, which is something we can use for, well, parsing paths. Inside a &lt;code&gt;Canvas&lt;/code&gt; component&amp;#39;s block, we map through the path strings, parse them, and then draw the path on canvas:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Canvas(...) {
    paths.map { (pathString, color) -&amp;gt;
        val parsedPath = PathParser()
          .parsePathString(pathString)
          .toPath()

        drawPath(
            path = parsedPath,
            color = color,
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PathParser&lt;/code&gt;&amp;#39;s method &lt;code&gt;parsePathString&lt;/code&gt; takes care of the parsing and returns a &lt;code&gt;PathParser&lt;/code&gt;-object. Then, we call the &lt;code&gt;toPath&lt;/code&gt; conversion method to get the &lt;code&gt;Path&lt;/code&gt; out of &lt;code&gt;PathParser&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;With these changes, we get the following image:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/dggvSWm62yfxmG1swDuhO/24d4c2df1213492e2cf28c4c784bcf30/non-binary-hearts-unscaled.jpg&quot; alt=&quot;Eighth hand-drawn hearts in yellow, white, purple, and black, that take only the top left quarter of the image.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you might notice, the image doesn&amp;#39;t scale to the entire space available. The reason is that SVG code is hard-coded to fit a specific size. If you look at the path strings above, you can see that they contain numbers as coordinates - they&amp;#39;re inside the (in our case) 278 x 270 area defined in the original SVG as size. &lt;/p&gt;
&lt;p&gt;To fix this problem, we can add some scaling functions into the mix. Let&amp;#39;s talk about that next.&lt;/p&gt;
&lt;h2 id=&quot;scaling&quot;&gt;Scaling&lt;/h2&gt;
&lt;p&gt;We&amp;#39;ll need to convert the parsed path to  &lt;code&gt;PathNode&lt;/code&gt;s to scale the paths. This way, we can scale every &lt;code&gt;moveTo&lt;/code&gt;, &lt;code&gt;curveTo&lt;/code&gt;, and other SVG drawing functions to the correct size. &lt;/p&gt;
&lt;p&gt;We&amp;#39;ll need a couple of helper functions to scale the paths on Canvas. One is a function for transforming float values from one size to another, and the other is a function that handles &lt;code&gt;PathNode&lt;/code&gt;&amp;#39;s scaling.&lt;/p&gt;
&lt;p&gt;This is how we define the float scaling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private fun Float.scaleToSize(
    oldSize: Float,
    newSize: Float,
): Float {
    val ratio = newSize / oldSize
    return this * ratio
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function takes in the old size (so, for example, the full old width), and new size (the new full width). With those values, we calculate a ratio to convert the float value by multiplying the value with the calculated ratio.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;PathNode&lt;/code&gt;&amp;#39;s scaling requires a bit more code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private fun PathNode.scaleTo(size: Size): PathNode {
    val originalWidth = 278f
    val originalHeight = 207f

    return when (this) {
        is PathNode.CurveTo -&amp;gt;
            this.copy(
                x1 = x1.scaleToSize(originalWidth, size.width),
                x2 = x2.scaleToSize(originalWidth, size.width),
                x3 = x3.scaleToSize(originalWidth, size.width),
                y1 = y1.scaleToSize(originalHeight, size.height),
                y2 = y2.scaleToSize(originalHeight, size.height),
                y3 = y3.scaleToSize(originalHeight, size.height),
            )
        is PathNode.MoveTo -&amp;gt;
            this.copy(
                x = x.scaleToSize(originalWidth, size.width),
                y = y.scaleToSize(originalHeight, size.height),
            )
        else -&amp;gt; this
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&amp;#39;re scaling the values from the original size to the Canvas size for each type of &lt;code&gt;PathNode&lt;/code&gt;. In the when-clause, we&amp;#39;re handling only &lt;code&gt;CurveTo&lt;/code&gt; and &lt;code&gt;MoveTo&lt;/code&gt;, because those are the only commands our path strings contain. If there were others, they should be handled here too. &lt;/p&gt;
&lt;p&gt;Scaling of each parameter utilizes the &lt;code&gt;scaleToSize&lt;/code&gt; we defined previously. The parameters we&amp;#39;re passing to the function depend on if the parameter is on the x or y-axis - if it&amp;#39;s on the x, we pass in width (as that&amp;#39;s the x-axis), and if it is on the y-axis, then we pass in height.  &lt;/p&gt;
&lt;p&gt;Now that we have the helper functions let&amp;#39;s change the mapping of path strings a bit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;paths.map { (pathString, color) -&amp;gt;
    val parsedPath =
        PathParser()
          .parsePathString(pathString)
          .toNodes()
          .map { it.scaleTo(size) }
          .toPath()

    drawPath(
        path = parsedPath,
        color = color,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we first parse the path, then convert the &lt;code&gt;PathParser&lt;/code&gt; it returns with &lt;code&gt;toNodes&lt;/code&gt; to a list of &lt;code&gt;PathNode&lt;/code&gt;s. We then map through each &lt;code&gt;PathNode&lt;/code&gt;, and scale it to size. Finally, we turn the scaled &lt;code&gt;PathNode&lt;/code&gt; list into &lt;code&gt;Path&lt;/code&gt;, which we can then draw. &lt;/p&gt;
&lt;p&gt;After these changes, the picture scales nicely:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/IZTHJ3XXdxaLK7g9KiQya/e8719defb2c0b496d2a21127c52d58ba/non-binary-hearts.jpg&quot; alt=&quot;Eighth hand-drawn hearts in yellow, white, purple, and black, which now scale the whole image&amp;#39;s area.&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked into how to turn SVG into &lt;code&gt;Path&lt;/code&gt;s that can be used in Compose&amp;#39;s canvas. This approach works for both native Android development, and Compose Multiplatform projects. &lt;/p&gt;
&lt;p&gt;You can find the complete code from this &lt;a href=&quot;https://gist.github.com/eevajonnapanula/9a0d70dd6ecfcc4cad17a5543be97f88&quot;&gt;Github gist&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.eevajonna.neuleart&quot;&gt;Neule.art&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-11-10/not-a-phase-text-with-compose-and-canvas/&quot;&gt;Not a Phase - Text with Compose and Canvas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-10-17/first-impressions-of-compose-multiplatform/&quot;&gt;First Impressions of Compose Multiplatform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.figma.com/community/file/1099013061143326702&quot;&gt;Sarah Laroche&amp;#39;s Vector Heart Figma resource&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG&quot;&gt;MDN&amp;#39;s documentation on SVGs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/eevajonnapanula/9a0d70dd6ecfcc4cad17a5543be97f88&quot;&gt;Github gist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>January 2025, You Were Rough</title>
    <link href="https://eevis.codes/blog/2025-02-02/january-2025-you-were-rough/" />
    <updated>2025-02-02T13:56:28.084Z</updated>
    <id>https://eevis.codes/blog/2025-02-02/january-2025-you-were-rough/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/31rYoLAM6BBG40pDW1876C/8093ea8938205615c64b436c49aa774d/2025-bingo-january-square.png"/>]]>
      &lt;p&gt;The first month of 2025 and its 247 days are behind us. I started 2025 with a blog post participating in &lt;a href=&quot;https://dev.to/challenges/newyear&quot;&gt;Dev&amp;#39;s New Year Challenge&lt;/a&gt;, sharing some hopes and fears for 2025. &lt;/p&gt;
&lt;p&gt;In the post, I shared two bingo cards: One for things I wish would happen, and one for things I think will, unfortunately, happen. The original blog post is behind this link: &lt;a href=&quot;https://dev.to/eevajonnapanula/2025-please-be-gentle-289e&quot;&gt;2025, Please, Be Gentle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I decided that after each month, I&amp;#39;d look at how it went in terms of these two cards. This is the first checkpoint, so let&amp;#39;s discuss the good stuff first. &lt;/p&gt;
&lt;h2 id=&quot;the-good-stuff&quot;&gt;The Good Stuff&lt;/h2&gt;
&lt;p&gt;From January, I can mark one thing as complete on my wishes-bingo card: My blog post, &lt;a href=&quot;https://dev.to/eevajonnapanula/using-svgs-on-canvas-with-compose-multiplatform-272p&quot;&gt;Using SVGs on Canvas with Compose Multiplatform&lt;/a&gt;, was selected to Dev&amp;#39;s week&amp;#39;s top 7 listing. I was so happy when I saw the notification about it!&lt;/p&gt;
&lt;p&gt;So, after a month, my &amp;quot;The Good Stuff&amp;quot;-bingo card looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/6x1WKrg3W3igKRHbG2TGNH/41fe5c4ba95cbf5486f4711add463437/2025_-_The_Good_Stuff_-_January.png&quot; alt=&quot;Bingo card with my wishes for 2025, and one item has a circle around it.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And the text version:&lt;/p&gt;
&lt;div class=&quot;table-container&quot; tabIndex=&quot;0&quot;&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;Learn a new skill&lt;/td&gt;
&lt;td&gt;More non-binary representation in conferences&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&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&#39;s top 7&lt;/td&gt;
 &lt;td&gt;#WeCoded without jerks commenting&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Conference swag socks in my size&lt;/td&gt;
 &lt;td&gt;Someone says my work inspired them&lt;/td&gt;
 &lt;td&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;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;h2 id=&quot;the-more-realistic&quot;&gt;The More Realistic&lt;/h2&gt;
&lt;p&gt;The title of this blog post is both about the state of the world right now - as a non-binary, disabled woman who is willing to call a nazi salute a nazi salute, everything that&amp;#39;s happening is just a lot. But it&amp;#39;s also about the bingo - I could mark several of my &amp;quot;More realistic&amp;quot; bingo card items as &amp;quot;This happened&amp;quot;. &lt;/p&gt;
&lt;p&gt;During January, I saw so much biased AI-generated content enforcing gender stereotypes. I also was addressed as a &amp;quot;guy&amp;quot; many times. And in countless discussions, when talking about a developer, that hypothetical developer was addressed as &amp;quot;he&amp;quot;. &lt;/p&gt;
&lt;p&gt;I was also told how I would be heard better if I wasn&amp;#39;t so negative and angry all the time - &amp;quot;just look at how this one man does it&amp;quot;. Side note: I was never angry, and my &amp;quot;negativity&amp;quot; in the situation was me being realistic, not negative. &lt;/p&gt;
&lt;p&gt;And finally, there was this one situation where I had told about something needing to be done and was met with &amp;quot;no&amp;quot;, but when a fellow developer, who is a man, brought it up a couple of weeks later, there was no discussion if it has to be done, but how we&amp;#39;re going to do it. And I bet no one else noticed this happening. &lt;/p&gt;
&lt;p&gt;So yeah, after January, my more realistic Bingo card looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/7CfCRzsSVT42li94IrpAAQ/723192590880b6bdb69428033cbf2353/More_Realistic_2025_-_January.png&quot; alt=&quot;Bingo card with more realistic content, and five items have a circle around them.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And the text version, with &amp;quot;Happened&amp;quot; before the text.&lt;/p&gt;
&lt;div class=&quot;table-container&quot; tabIndex=&quot;0&quot;&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;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;Man speaks over me&lt;/td&gt;
 &lt;td&gt;Called &quot;intimidating&quot; for being assertive.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Someone interrupts when I&#39;m presenting&lt;/td&gt;
 &lt;td&gt;&quot;Gender doesn&#39;t matter, just talent&quot;&lt;/td&gt;
 &lt;td&gt;Man takes credit for what I said&lt;/td&gt;
 &lt;td&gt;Being left out of an important meeting&lt;/td&gt;
 &lt;td&gt;&lt;b&gt;Happened:&lt;/b&gt; &quot;You&#39;d be heard better, if you weren&#39;t so angry&quot;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Someone assumes I can&#39;t handle my tech setup&lt;/td&gt;
 &lt;td&gt;Meeting a new person who assumes I&#39;m not a developer&lt;/td&gt;
 &lt;td&gt;&quot;I&#39;ve never seen anyone being discriminated&quot;&lt;/td&gt;
 &lt;td&gt;&quot;Intent over impact&quot;&lt;/td&gt;
 &lt;td&gt;&lt;b&gt;Happened:&lt;/b&gt; Man needs to repeat what I&#39;ve said&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Invalidation of lived experience&lt;/td&gt;
 &lt;td&gt;&quot;Women just don&#39;t want to code&quot;&lt;/td&gt;
 &lt;td&gt;Someone else credited for my work&lt;/td&gt;
 &lt;td&gt;&quot;But they clearly didn&#39;t mean it&quot; &lt;/td&gt;
 &lt;td&gt;Sexist comment in a blog post or talk&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&quot;You were hired just for diversity quotas&quot;&lt;/td&gt;
 &lt;td&gt;Everyone&#39;s surprised after something I warned happens&lt;/td&gt;
 &lt;td&gt;Man splaining my expertise to me&lt;/td&gt;
 &lt;td&gt;&quot;Equality has gone too far&quot;&lt;/td&gt;
 &lt;td&gt;&lt;b&gt;Happened:&lt;/b&gt; Hypothetical developer is referred to as &quot;he&quot; or other gendered noun&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;While my more realistic bingo card got more marks than the good stuff one this month, and the state of the world is what it is, January was not all bad. There were a lot of great conversations, accomplishments, and other small, positive things. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s hope that February brings more good, both to the world and to my life!&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Be Mine and Add Interaction with Compose and Canvas</title>
    <link href="https://eevis.codes/blog/2025-02-13/be-mine-and-add-interaction-with-compose-and-canvas/" />
    <updated>2025-02-13T05:33:02.206Z</updated>
    <id>https://eevis.codes/blog/2025-02-13/be-mine-and-add-interaction-with-compose-and-canvas/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2ec3A7sOxtTdhclUsFpuMe/9556a06e41b9e2c13535511a7d2eca03/be-mine-square.png"/>]]>
      &lt;p&gt;Valentine&amp;#39;s Day is approaching, and while I prefer the Finnish version (&amp;quot;Friend&amp;#39;s Day&amp;quot;), I was inspired to do some creative coding in the holiday theme. &lt;/p&gt;
&lt;p&gt;The Hollywood-centric film industry (in addition to globalization in general) has carried those heart-shaped candies to the North as well. While browsing some images for inspiration, I decided to use them for the next piece I&amp;#39;m writing.&lt;/p&gt;
&lt;p&gt;And that&amp;#39;s not all! I also wanted to continue my series of Canvas-related blog posts, so this one walks through another concept with Canvas: How to detect pointer input gestures and add some interaction.&lt;/p&gt;
&lt;p&gt;So, what are we building today? Here&amp;#39;s a video showing some heart-shaped candies with messages and how they scale bigger when I&amp;#39;m touching them:&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/12XxUKQZ8MTzWvWfrq8ZtF/346802db00e164fd632166d31600af31/1739250755014607.mp4&quot; controls=&quot;&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s get coding!&lt;/p&gt;
&lt;h2 id=&quot;drawing-the-hearts&quot;&gt;Drawing the Hearts&lt;/h2&gt;
&lt;p&gt;This project builds on my previous blog post, &lt;a href=&quot;https://eevis.codes/blog/2025-01-15/using-svgs-on-canvas-with-compose-multiplatform/&quot;&gt;Using SVGs on Canvas with Compose Multiplatform&lt;/a&gt;, which explained how to draw from SVG&amp;#39;s path strings to Compose Canvas. I prepared an SVG image on Figma and then extracted the path strings to draw the hearts.&lt;/p&gt;
&lt;p&gt;We will utilize some functions from the previous blog post: &lt;code&gt;Float.scaleToSize&lt;/code&gt; and &lt;code&gt;PathNode.scaleTo&lt;/code&gt;. I won&amp;#39;t explain them in this blog post; just mention them. If you want a refresher on how they work, head to the blog post describing them. &lt;/p&gt;
&lt;p&gt;Alright, let&amp;#39;s start by drawing one heart on Canvas. &lt;/p&gt;
&lt;h3 id=&quot;drawing-one-heart&quot;&gt;Drawing One Heart&lt;/h3&gt;
&lt;p&gt;We want to extract the logic for drawing the heart to a function. Let&amp;#39;s call it &lt;code&gt;drawCandyHeart&lt;/code&gt;, and pass the top-left offset, the size of the heart, and the color we&amp;#39;re drawing the heart with to the function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private fun DrawScope.drawCandyHeart(
    topLeft: Offset,
    heartSize: Size,
    color: Color,
) {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&amp;#39;ve defined the path strings in a separate list, but I will leave them out from this blog post for brevity. They&amp;#39;re included in the final code, which you can find at the end of the blog post. &lt;/p&gt;
&lt;p&gt;We can get the path strings with a function &lt;code&gt;pathString(color: Color): Pair&amp;lt;String, Color&amp;gt;&lt;/code&gt;, which returns the two path strings with the color with different opacity values to get the look we aim for. &lt;/p&gt;
&lt;p&gt;So, using the function to get the path strings with color, let&amp;#39;s first parse the strings into &lt;code&gt;Path&lt;/code&gt;s:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;HeartCandy.Heart
    .pathStrings(color)
    .map { (pathString, color) -&amp;gt;
        val path =
            PathParser()
                .parsePathString(pathString)
                .toNodes()
                .map { path -&amp;gt; path.scaleTo(heartSize.height) }
                .toPath()
                .apply {
                    translate(topLeft)
                }
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we go through each path string and color pair, and using &lt;code&gt;PathParser&lt;/code&gt;, we parse the path strings into &lt;code&gt;Path&lt;/code&gt;s. Then, we convert the &lt;code&gt;Path&lt;/code&gt; into &lt;code&gt;Node&lt;/code&gt;s, so we can scale them to the correct size. Once that&amp;#39;s done, we convert them back to &lt;code&gt;Path&lt;/code&gt;s, and then move them to correct position. &lt;/p&gt;
&lt;p&gt;After that, we can use the parsed path to draw the candy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawPath(
    path = path,
    color = color,
)
drawPath(
    path = path,
    color = color,
    style = Stroke(width = 2f),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We draw the path twice to get the fill and then to draw the stroke around the shapes. After these steps, our heart looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/kTwkxI57yrEIDTxChwbaf/5563298d1788f682799f34dccbe9dab7/Screenshot_2025-02-10_at_7.08.56.png&quot; alt=&quot;A pink heart candy shaped heart on a peach background.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The next step is to add some text to the candy.&lt;/p&gt;
&lt;h3 id=&quot;adding-the-text&quot;&gt;Adding the Text&lt;/h3&gt;
&lt;p&gt;To draw the text on Canvas, we need the text as &lt;code&gt;TextLayoutResult&lt;/code&gt;, so let&amp;#39;s add a new parameter to &lt;code&gt;drawHeartCandy&lt;/code&gt; called &lt;code&gt;text&lt;/code&gt;, which is of type &lt;code&gt;TextLayoutResult&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private fun DrawScope.drawCandyHeart(
    ...
    text: TextLayoutResult,
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, we can handle all text-related changes in the parent component without passing a &lt;code&gt;TextMeasurer&lt;/code&gt; to the drawing component, as the &lt;code&gt;drawText&lt;/code&gt; method needs the text as &lt;code&gt;TextLayoutResult&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We pass in the text we want to display, which we also measure with &lt;code&gt;textMeasurer&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val textMeasurer = rememberTextMeasurer()
...
drawCandyHeart(
   ...
    text = textMeasurer.measure(
        text = &amp;quot;Enby&#92;nLove&amp;quot;.uppercase(),
        style = TextStyle.Default.copy(
            fontFamily = RighteousFontFamily,
            textAlign = TextAlign.Center,
            fontSize = 10.sp,
        ),
    )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also style the text here by turning it all uppercase and defining its style. If you&amp;#39;re wondering about the font family (Righteous), that&amp;#39;s imported to the code as described in &lt;a href=&quot;https://eevis.codes/blog/2024-11-10/not-a-phase-text-with-compose-and-canvas/&quot;&gt;Not a Phase - Text with Compose and Canvas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The next step is to draw the text. Our code looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;rotate(
    degrees = -7f,
    pivot = Offset(
        x = topLeft.x + heartSize.width * 0.5f,
        y = topLeft.y + heartSize.height * 0.5f,
    ),
) {
    drawText(
        textLayoutResult = text,
        topLeft = calculateTextTopLeft(
            topLeft = topLeft,
            text = text,
            heartWidth = heartSize.width,
            padding = 5.dp.toPx()
        ),
        color = color,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What&amp;#39;s worth noting here is that we need to rotate the text slightly to align with the heart correctly. Also, we calculate the top left coordinates for the text with the following function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private fun calculateTextTopLeft(
    topLeft: Offset,
    text: TextLayoutResult,
    heartWidth: Float,
    padding: Float
) = Offset(
    y = topLeft.y + (text.size.height * (1f / text.lineCount) + padding),
    x = topLeft.x + heartWidth * 0.5f - text.size.width * 0.3f,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After these changes, the heart looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/tVxiblzNdzoEJAGkw4m2d/ba314691388cc88c9135e15f80e161e8/Screenshot_2025-02-10_at_7.09.04.png&quot; alt=&quot;A pink heart candy shaped heart on peach background with text &amp;#39;enby love&amp;#39;.&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;drawing-them-all&quot;&gt;Drawing Them All&lt;/h3&gt;
&lt;p&gt;Okay, we have one heart. But the final goal is to have 16 of them. I&amp;#39;ve defined the color and text pairs outside the component as the type of &lt;code&gt;List&amp;lt;Pair&amp;lt;Color, String&amp;gt;&amp;gt;&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;To create the 4 x 4 layout for the hearts, we&amp;#39;re going to first chunk the list of items into four sublists and then map through each item in the row:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;HeartCandy.hearts
    .chunked(4)
    .mapIndexed { row, colors -&amp;gt;
        colors.mapIndexed { index, (color, text) -&amp;gt;
            ...
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, extending and generalizing the code we wrote for the one heart, inside the &lt;code&gt;colors.mapIndexed&lt;/code&gt; we call:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawCandyHeart(
    topLeft = Offset(
        x = index * (heartSize.width + horizontalPadding),
        y = row * (heartSize.height + verticalPadding),
    ),
    heartSize = HeartCandy.Heart.size,
    color = color,
    text = textMeasurer.measure(
        text = text.uppercase(),
        style = HeartCandy.Heart.textStyle,
    )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use the index on the column and row to add enough offset to each heart to position them correctly. We also add horizontal and vertical padding, which is calculated based on the remaining space on the screen. &lt;/p&gt;
&lt;p&gt; We pass in the color and use the text from the item instead of the hard-coded text we had for the one heart. Finally, the text style has been extracted to an object to save space, compared to the code for drawing one heart.&lt;/p&gt;
&lt;p&gt;After these changes, our piece of art looks like this: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/4xT2meKIygfhrssLaU4QhB/144541393a942053d21f72a926d65a23/Screenshot_2025-02-11_at_6.13.59.png&quot; alt=&quot;16 hearts with different colors and texts lick me, queer joy, xo xo, we&amp;#39;re here, queer cutie, enby love, ace pal, bi bestie, love is love, love me, u belong, they them, queer &amp;amp; here, U R valid, eat the rich, and be mine. &quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;adding-interaction&quot;&gt;Adding Interaction&lt;/h2&gt;
&lt;p&gt;Finally, we&amp;#39;re at the point where we&amp;#39;re adding some interaction! First, we need to store one variable and add a modifier in the parent component to enable dragging gestures:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun HeartCandy() {
    var dragPosition by remember { mutableStateOf(Offset.Zero) }
    ... 

    Canvas(
        modifier =  Modifier
            ...
            .pointerInput(Unit) {
                detectDragGestures(
                    onDragEnd = {
                        dragPosition = Offset.Zero
                    },
                ) { change, _ -&amp;gt;
                    dragPosition = dragPosition.copy(
                        x = change.position.x,
                        y = change.position.y
                    )
                }
            },
        ) { ... }
   }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We define a variable to store the dragging position, add the &lt;code&gt;pointerInput&lt;/code&gt;-modifier, and then call the &lt;code&gt;detectDragGestures&lt;/code&gt; inside the modifier. Then, when a drag gesture is detected, we set the changed position to the &lt;code&gt;dragPosition&lt;/code&gt;-variable. When the drag gesture ends, we set the position to default value, so, in this case, &lt;code&gt;Offset.Zero&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;We now know where the user&amp;#39;s pointer input is moving. Next, we&amp;#39;ll want to use that information and transform a heart when the pointer input touches it. First, let&amp;#39;s pass the drag position to the &lt;code&gt;drawCandyHeart&lt;/code&gt;-method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private fun DrawScope.drawCandyHeart(
    dragPosition: Offset,
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we want to know if the drag spot is within the drawn paths for the hearts. &lt;code&gt;Path&lt;/code&gt; provides an excellent method: &lt;code&gt;getBounds&lt;/code&gt;, which returns the path&amp;#39;s bounds as a &lt;code&gt;Rect&lt;/code&gt;. Then, we can check if this &lt;code&gt;Rect&lt;/code&gt; contains the current drag position. &lt;/p&gt;
&lt;p&gt;There is just one thing: We have two different paths for the heart, so we want to check if the drag spot is within either of them to do transformations. We&amp;#39;ll need to refactor our way of drawing a bit by dividing the path parsing from the path drawing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val paths = HeartCandy.Heart
    .pathStrings(color)
    .map { (pathString, pathColor) -&amp;gt;
         val path = // Parse the path
         Pair(path, pathColor)
    }

val pathBounds = paths.map { it.first.getBounds() }
val isSelected = pathBounds.any { it.contains(dragPosition) }

paths
    .map { (path, color) -&amp;gt;
        // Draw the paths
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We first draw the paths as we did before, and from the map, we return the data as &lt;code&gt;Pair&amp;lt;Path, String&amp;gt;&lt;/code&gt;. Then, we map through the paths to get the bounds for each path and store the result in &lt;code&gt;pathBounds&lt;/code&gt;. After that, we map through it, checking if any of them contain the current drag position. &lt;/p&gt;
&lt;p&gt;Now that we know if a heart is currently selected, we can do some transformations. Let&amp;#39;s do two things: Scale the heart slightly bigger and change the text color.&lt;/p&gt;
&lt;p&gt;First, let&amp;#39;s define the variables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val scaleAmount = if (isSelected) 1.05f else 1f
val textColor = if (isSelected) HeartCandy.highlightColor else color
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we want to wrap everything we&amp;#39;re drawing with the scale transform function and pass in the scale amount:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;scale(scaleAmount) {
    paths.map {...}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, change the text color to the newly defined variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;drawText(
    ...
    color = textColor,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&amp;#39;s it. With these code changes, we&amp;#39;ve added some interaction to our canvas. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked into how to add interaction on Compose Canvas. We drew some Valentine&amp;#39;s day themed hearts with a queer twist and added an ability to select each of them with pointer input. &lt;a href=&quot;https://gist.github.com/eevajonnapanula/ce88dc670e10fcaac255e1ed5b70e37d&quot;&gt;The complete code is available in this code snippet.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;While this project was fun to create, my inner accessibility specialist is reminding me about a few things: First of all, the text in the candies doesn&amp;#39;t have enough contrast between the text and the background to be legible. The other thing is that right now, all the interactions are available only for pointer input, leaving out anyone using assistive technologies that don&amp;#39;t support the pointer input. &lt;/p&gt;
&lt;p&gt;I might write a blog post about improving these aspects later.&lt;/p&gt;
&lt;p&gt;Have you built something exciting and interactive with Compose and Canvas? Or do you have future plans?&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2025-01-15/using-svgs-on-canvas-with-compose-multiplatform/&quot;&gt;Using SVGs on Canvas with Compose Multiplatform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-11-10/not-a-phase-text-with-compose-and-canvas/&quot;&gt;Not a Phase - Text with Compose and Canvas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/eevajonnapanula/ce88dc670e10fcaac255e1ed5b70e37d&quot;&gt;The complete code is available in this code snippet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Making basicMarquee-Modifier More Accessible</title>
    <link href="https://eevis.codes/blog/2025-02-20/making-basicmarquee-modifier-more-accessible/" />
    <updated>2025-02-20T07:17:01.787Z</updated>
    <id>https://eevis.codes/blog/2025-02-20/making-basicmarquee-modifier-more-accessible/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3ljt1OeaD2iTzqb4EOiH9x/9d0735a8b00c209f9cbc40e15a7646f6/basic-marquee-square__2_.png"/>]]>
      &lt;p&gt;I have this &amp;quot;Remove animations&amp;quot;-setting turned on on my phone because different kinds of movement make me feel physically sick. When the setting is on, animations are usually removed from native Android apps. &lt;/p&gt;
&lt;p&gt;And when there are some animations, I notice them. I&amp;#39;ve started seeing more and more of some horizontally scrolling texts, and I have been wondering why. &lt;/p&gt;
&lt;p&gt;Then I came across this &lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).basicMarquee(kotlin.Int,androidx.compose.foundation.MarqueeAnimationMode,kotlin.Int,kotlin.Int,androidx.compose.foundation.MarqueeSpacing,androidx.compose.ui.unit.Dp)&quot;&gt;&lt;code&gt;basicMarquee&lt;/code&gt;&lt;/a&gt;-modifier. And it doesn&amp;#39;t respect the &amp;quot;Remove animations&amp;quot; accessibility setting. And that&amp;#39;s bad - many users (me included) rely on that setting to not see animations. Marquee-styled animations are one of the worst triggers of my motion sickness symptoms. &lt;/p&gt;
&lt;p&gt;If you want to learn more about my symptoms and why animations can be super problematic for some, I&amp;#39;ve written two blog posts, one from Android and one from a web point of view:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Android, Animations and Reduced Motion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/eevajonnapanula/you-make-my-head-spin-reducing-the-motion-on-web-328b&quot;&gt;You Make My Head Spin - Reducing the Motion on Web&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But since bringing up solutions is often better received than bringing up problems, this blog post will demonstrate one idea on how to improve the situation for us who rely on that &amp;quot;Remove animations&amp;quot; setting.&lt;/p&gt;
&lt;p&gt;The main idea is to read the value of this setting and then use it to decide if the &lt;code&gt;basicMarquee&lt;/code&gt;-modifier is used. In this blog post, I&amp;#39;m using composition locals to accomplish it. &lt;/p&gt;
&lt;h2 id=&quot;remove-animations-setting&quot;&gt;Remove Animations Setting&lt;/h2&gt;
&lt;p&gt;Before we dive into the code, a couple of words about the &amp;quot;Remove animations&amp;quot; setting. It&amp;#39;s a global setting, which you can find from the accessibility settings. In my Pixel phone, &amp;quot;Remove animations&amp;quot; is under the &amp;quot;Color and motion&amp;quot;-section. &lt;/p&gt;
&lt;p&gt;From a technical perspective, the setting changes the animator duration scale to 0, so, in other words, the animations end right after they start. This accessibility setting is not exposed via accessibility services the same way as, for example, screen reader availability, so we&amp;#39;ll need to be a bit creative. The following section explains how we can read the value of this setting. &lt;/p&gt;
&lt;h2 id=&quot;composition-local-for-remove-animations&quot;&gt;Composition Local for Remove Animations&lt;/h2&gt;
&lt;p&gt;We&amp;#39;ll want to store the value of the &amp;quot;Remove animations&amp;quot; setting and make it available for the components in the component hierarchy. Custom &lt;a href=&quot;https://developer.android.com/develop/ui/compose/compositionlocal&quot;&gt;&lt;code&gt;CompositionLocal&lt;/code&gt;&lt;/a&gt; is one tool for that. As Android documentation describes &lt;code&gt;CompositionLocal&lt;/code&gt;s:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;CompositionLocal&lt;/code&gt; is a tool for passing data down through the Composition implicitly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;#39;s start by creating the custom composition local and the data type it provides:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// LocalRemoveAnimations.kt

data class RemoveAnimations(
    val context: Context
) {
    val enabled: Boolean
}

val LocalRemoveAnimations =
    staticCompositionLocalOf&amp;lt;RemoveAnimations&amp;gt; {
        error(&amp;quot;No user found!&amp;quot;)
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we&amp;#39;re defining the &lt;code&gt;LocalRemoveAnimations&lt;/code&gt; as &lt;code&gt;staticCompositionLocalOf&lt;/code&gt; as the value is not likely to change - in fact, the app needs to be restarted to test it. Or at least I haven&amp;#39;t found a way to observe the value; just read it synchronously. &lt;/p&gt;
&lt;p&gt;We can read the value of the animation duration scale with the following code: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val animationDuration = try {
    Settings.Global.getFloat(
        context.contentResolver,
        Settings.Global.ANIMATOR_DURATION_SCALE,
        1f
    )
} catch (e: Settings.SettingNotFoundException) {
    1f
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, we try to read the value of &lt;code&gt;Settings.Global.ANIMATOR_DURATION_SCALE&lt;/code&gt; with a default value of 1f. If the setting is not found, &lt;code&gt;Settings.SettingNotFoundException&lt;/code&gt; is thrown; we want to catch it and set the default value. One example of when the setting is unavailable is when the phone&amp;#39;s Android version is older than 12. &lt;/p&gt;
&lt;p&gt;And putting them together, we get:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// LocalRemoveAnimations.kt

data class RemoveAnimations(
    val context: Context,
) {
    val enabled: Boolean
        get() =
            try {
                Settings.Global.getFloat(
                    context.contentResolver,
                    Settings.Global.ANIMATOR_DURATION_SCALE,
                    1f,
                )
            } catch (e: Settings.SettingNotFoundException) {
                1f
            } == 0f
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that for the &lt;code&gt;enabled&lt;/code&gt;-value, we add a check if the value from the try/catch-block is 0f to know if the &amp;quot;Remove animations&amp;quot; setting is enabled.&lt;/p&gt;
&lt;p&gt;The next step is to provide it in code: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MainActivity.kt

setContent {
    val context = LocalContext.current

    CompositionLocalProvider(
        LocalRemoveAnimations provides
            RemoveAnimations(
                context = context,
            ),
    ) { 
            // App content goes here
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we can read the value of the &lt;code&gt;LocalRemoveAnimations&lt;/code&gt; in the components inside the app:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun ComponentSomewhereInTheHierarchy() {
    val removeAnimations = LocalRemoveAnimations.current.enabled
    ....
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All right, now we have everything to build the safer marquee modifier. Let&amp;#39;s do that in the next section.&lt;/p&gt;
&lt;h2 id=&quot;safemarquee-modifier&quot;&gt;&lt;code&gt;safeMarquee&lt;/code&gt;-Modifier&lt;/h2&gt;
&lt;p&gt;Now that we have the information about the user&amp;#39;s &amp;quot;Remove animations&amp;quot;-setting available via &lt;code&gt;LocalRemoveAnimations&lt;/code&gt;, we can use it to adjust the text using the &lt;code&gt;basicMarquee&lt;/code&gt;-modifier. &lt;/p&gt;
&lt;p&gt;If the user hasn&amp;#39;t enabled the &amp;quot;Remove animations&amp;quot; setting, we want to show the marquee; otherwise, let the text flow on multiple lines. Let&amp;#39;s define a custom modifier with the composable modifier factory and call it &lt;code&gt;safeMarquee&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MarqueeScreen.kt

@Composable
fun Modifier.safeMarquee(): Modifier {  

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we want to read the value from &lt;code&gt;LocalRemoveAnimations&lt;/code&gt; and, using that information, either add the &lt;code&gt;basicMarquee&lt;/code&gt;-modifier to the modifier chain or return the current modifier chain without any additional ones. Here&amp;#39;s the code to do so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// MarqueeScreen.kt

@Composable
fun Modifier.safeMarquee(): Modifier {  
    val animationsRemoved = LocalRemoveAnimations.current.isEnabled()

    return if (animationsRemoved) 
        this 
    else 
        this then basicMarquee()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these code changes, we get the desired result. Here&amp;#39;s a video showing a preview of a &lt;code&gt;Text&lt;/code&gt;-component with the &lt;code&gt;safeMarquee&lt;/code&gt;-modifier:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/3Qtl9Y6TvymbHXbDO7tm67/fd3d0e24989b582646314019c182c3d3/1739940650785658.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, we&amp;#39;ve looked into how to make the &lt;code&gt;basicMarquee&lt;/code&gt; modifier more accessible for users with the &amp;quot;Remove animations&amp;quot;-setting turned on. We checked the value of this setting, stored it as static composition local, and then used it to decide if we add the &lt;code&gt;basicMarquee&lt;/code&gt; to the element.&lt;/p&gt;
&lt;p&gt;Do you use the &amp;quot;Remove animations&amp;quot;-setting? Or have you encountered problems with it as a user or developer? &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).basicMarquee(kotlin.Int,androidx.compose.foundation.MarqueeAnimationMode,kotlin.Int,kotlin.Int,androidx.compose.foundation.MarqueeSpacing,androidx.compose.ui.unit.Dp)&quot;&gt;&lt;code&gt;basicMarquee&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Android, Animations and Reduced Motion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/eevajonnapanula/you-make-my-head-spin-reducing-the-motion-on-web-328b&quot;&gt;You Make My Head Spin - Reducing the Motion on Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/develop/ui/compose/compositionlocal&quot;&gt;&lt;code&gt;CompositionLocal&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Echoes of the Past - Tech is Still Not Equal for All</title>
    <link href="https://eevis.codes/blog/2025-03-10/echoes-of-the-past-tech-is-still-not-equal-for-all/" />
    <updated>2025-03-10T07:37:01.849Z</updated>
    <id>https://eevis.codes/blog/2025-03-10/echoes-of-the-past-tech-is-still-not-equal-for-all/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/37qhFCoQdxmQc2bvoDWLHB/9e675467c87499f851384e4864d9617a/we-coded-square.png"/>]]>
      &lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href=&quot;https://dev.to/challenges/wecoded&quot;&gt;WeCoded Challenge&lt;/a&gt;: Echoes of Experience&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Earth has traveled around the Sun, and it&amp;#39;s time for the We Coded again! I&amp;#39;ve been waiting for this for a year because I have things to say. I planned parts of this blog post a year ago, so it&amp;#39;s been a long wait. &lt;/p&gt;
&lt;p&gt;Little did I know then how the situation would change in a year. With all the things happening in the US, the fast rise of far-right ideologies in Europe, and the attacks on many minorities, including gender minorities, it&amp;#39;s even more important that such campaigns as We Coded exist. &lt;/p&gt;
&lt;p&gt;In the past years, I&amp;#39;ve shared my experiences as someone from a gender minority in tech, and I decided to continue with that this year, too. It&amp;#39;s been a heck of a year, and I can share only a fraction of what I&amp;#39;ve seen, but even that is too much. &lt;/p&gt;
&lt;p&gt;Oh, and this year, I decided to try out something new and recorded some of the experiences as a video. It&amp;#39;s embedded at the end of the blog post. &lt;/p&gt;
&lt;h2 id=&quot;blog-post-comments&quot;&gt;Blog Post Comments&lt;/h2&gt;
&lt;p&gt;So, first, some blog comments I&amp;#39;ve received. Many are from last year&amp;#39;s WeCoded post, and some have already been deleted because of misconduct. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s start with this one person who was first commenting on my blog post at Dev, and then, when they got blocked, they reached out to me in Polywork. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c4jvgeao4xv0v09g350h.png&quot; alt=&quot;Dev.to comment: At the end, you said you want someone as colleague whom you can use as dustbin for your emotional turmoil.. now that certainly can make the workplace insufferable if that person doesn&amp;#39;t have other motives towards you.. even you won&amp;#39;t be willing to be like that for someone else.. I suggest you better find that unfortunate one in your personal life, not at workplace where serious businesses get built&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So, there&amp;#39;s a lot to unpack there, but I&amp;#39;m not going too deep with it. Just note the &amp;quot;other motives&amp;quot;-part. The message from that person on Polywork was this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yuzxg21exl4bflsv2a4w.png&quot; alt=&quot;Polywork message: Thank you for thinking my comment on dev.to as contribution for your next piece of sweet post.. just letting you know.. your kind gets sidelined everywhere because of the complaning nature.. you don&amp;#39;t take even normal talk lightly.. you think everyone is trying to demean you.. so you complain everywhere wrongfully.. good devs don&amp;#39;t want to get into trouble by interacting with you.. getting into false metoo and all.. that&amp;#39;s why they turn their back in front you.. and the more you complain the more they will continue to do so World is not bed of roses as you might think.. everyone is struggling not just you.. so don&amp;#39;t expect special treatment from devs.. as you might be accustomed with on your facebook, tinder etc. profiles.. best wishes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Again, they&amp;#39;re referring to &amp;quot;false metoo&amp;quot; and &amp;quot;expecting special treatment as on Facebook or Tinder&amp;quot;. &lt;/p&gt;
&lt;p&gt;I&amp;#39;m just wondering, what are these people doing at work and in professional circles? Are they looking for a partner because they seem to assume that everything somehow relates to &amp;quot;other motives&amp;quot;, which I&amp;#39;m innocently interpreting as wanting to have a relationship. But most likely, sex plays a part here, too.&lt;/p&gt;
&lt;p&gt;And the thoughts about sex don&amp;#39;t stop there. I cross-posted the same blog post to Medium, and it got boosted and attracted many eyes. Blake (this was the name of the commenter) decided to comment on it. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iejqzd3x4wra6hfsivib.png&quot; alt=&quot;Comment on Medium: Being a former employer, I never cared about a person&amp;#39;s gender, race, sexual orientation, politics, or background. I only cared about how well they did their job. Why do you have to shove your sexuality down everyone&amp;#39;s throat? Perhaps that&amp;#39;s the problem!&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here again, &amp;quot;Why do you have to shove your sexuality down everyone&amp;#39;s throat&amp;quot;. And I must remind you - I did not talk about sex in my blog post. Just my experiences in tech, I repeat, nothing about sex. Not even anything about my sexual orientation. &lt;/p&gt;
&lt;p&gt;So... My only conclusion is that in Blake&amp;#39;s eyes, just by existing as an assumed woman, I&amp;#39;m shoving my sexuality down everyone&amp;#39;s throat. And I think that tells a lot about the blakes of this world, not about me. &lt;/p&gt;
&lt;p&gt;The blog posts I wrote last year got many other similar comments. They were about &amp;quot;gender wars&amp;quot;, how my experiences are invalid, and how I just should accept that this is what it is and leave it be. I&amp;#39;m not going to do that, by the way.&lt;/p&gt;
&lt;p&gt;And on a related note, there was one more comment I wanted to include in this post. The blog post was about creating more inclusive gender selection options in forms with Jetpack Compose, and this comment is what I got:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gp8xost0adz4ftyb2sg2.png&quot; alt=&quot;Comment on Medium: why tf you guys bring gender identity shit into everything? this article brings low to zero value as a compose UI developer.
I have read your other accessibility articles and even bookmarked them coz they;re great but this one just sucks.
There&amp;#39;s a reason why this article has zero claps.
Please get back to accessibility stuff of compose.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Oh, how I love it when someone tells me to stay in line and not talk about things that matter to me. [/s] Like these inclusive forms. You know, as a non-binary woman, forms asking my gender are most of the time just excluding me. &lt;/p&gt;
&lt;p&gt;Naturally, my reaction to this comment was to turn it up a notch. I&amp;#39;m not going to be silent about these things. &lt;/p&gt;
&lt;h2 id=&quot;speaking-experiences&quot;&gt;Speaking Experiences&lt;/h2&gt;
&lt;p&gt;I also do public speaking, and while not all conferences or talks attract non-nice things, there are always some. I want to share a couple of experiences from one conference last summer. &lt;/p&gt;
&lt;p&gt;I was giving a talk about accessibility. You know, that&amp;#39;s my specialty. I&amp;#39;ve been certified in it, and one could say I know a lot about it. Right before the talk, there was this one person who decided that it was a good idea to explain to me what accessibility is. Or, &amp;quot;splain&amp;quot; would be a better word (I&amp;#39;m not going to use &amp;quot;mansplain&amp;quot; because the last time I used it, someone was so hurt about it that it got nasty). &lt;/p&gt;
&lt;p&gt;Well, I was standing there, listening. I was too polite to stop them, and I was also nervous about the talk that was about to start in a couple of minutes, so I just stood there. And when I finally began my talk, I was super stressed. It was an awful feeling to start speaking to hundreds of people. &lt;/p&gt;
&lt;p&gt;But the talk went well, and I aced it. After the talk, I took my Q&amp;amp;A privately, so, not from the stage. I enjoy doing it that way because it allows for actual discussions with people, not just answering questions. And there were people! And so great questions! &lt;/p&gt;
&lt;p&gt;There was just this one thing - a person I met for the first time that day decided that it would be a good idea to answer the questions on my behalf. So there we were. Someone asked a question, and I tried to start answering faster than this person, and when I did, they had to add something to the answer after me. And this continued until we had to leave the room for the next talk to begin. &lt;/p&gt;
&lt;p&gt;I was shocked but didn&amp;#39;t want to seem impolite (yeah, I should work on that), so I didn&amp;#39;t say anything at the moment; I just tried to get through the situation. &lt;/p&gt;
&lt;p&gt;Later, I confronted them, and they told me they had good intentions and thought they would support me because I was speaking for the first time. &lt;/p&gt;
&lt;p&gt;I&amp;#39;ve been speaking since 2020. &lt;/p&gt;
&lt;p&gt;So, a tip: If you genuinely want to support someone, ask them what they need and want. Don&amp;#39;t assume. Most speakers want to answer the questions themselves that the audience comes to ask them. You know, just themselves, because they&amp;#39;re experts on the topic.&lt;/p&gt;
&lt;p&gt;After the talk and these incidents, I felt terrible. Like, I&amp;#39;ve been doing this for a while, and these people assume that I don&amp;#39;t know what I&amp;#39;m talking about. &lt;/p&gt;
&lt;p&gt;I doubt these things happen to men - or if they do, they&amp;#39;re rare. But when you come from a minority in tech, you often need to prove yourself multiple times, while someone from the majority is assumed to know their things right from the start.&lt;/p&gt;
&lt;h2 id=&quot;being-at-work&quot;&gt;Being At Work&lt;/h2&gt;
&lt;p&gt;Finally, I want to talk about work. Being a non-binary woman in a team where I&amp;#39;m often the only non-man, I see these biased things happening constantly.&lt;/p&gt;
&lt;p&gt;And I know no one on the team has bad intentions. They&amp;#39;ve just been in a man-dominated world and man-dominated field for so long that these biases are built-in. And many of them try to mitigate the bias, I know they do.&lt;/p&gt;
&lt;p&gt;I had to write the previous paragraph because I know that some of them will read this blog post, and I wanted to underline that I don&amp;#39;t assume that any of the things I&amp;#39;m saying were done because of some evilness, and I have no hard feelings towards them because of these things. &lt;/p&gt;
&lt;p&gt;It also tells a lot about the emotional work we women and non-binary people do to ensure we&amp;#39;re not misunderstood when speaking up.&lt;/p&gt;
&lt;p&gt;The past year has contained some classic biased situations. Like when I brought up a thing we must do and was greeted with, &amp;quot;No, we&amp;#39;re not doing it&amp;quot;. And then, a couple of weeks later, my man colleague brought it up, and the answer was straight &amp;quot;Yes&amp;quot;, without any discussions. &lt;/p&gt;
&lt;p&gt;There have been many situations where something has been asked about a topic I&amp;#39;m very knowledgeable in, and a man colleague, who has almost zero knowledge on the topic, has answered the question before me. I&amp;#39;ve also been forgotten in meetings - either forgotten to invite, or simply not given a turn to speak when everyone else was given a turn. Of course, I was apologized to later.&lt;/p&gt;
&lt;p&gt;These are just some examples, but they take their toll on me, year after year.&lt;/p&gt;
&lt;h2 id=&quot;final-words&quot;&gt;Final Words&lt;/h2&gt;
&lt;p&gt;So yeah, In the blog post, I&amp;#39;ve shared some of my experiences from the past year as someone from a gender minority in tech. I would love to say that it gets better, but on some things, it feels like it&amp;#39;s getting worse because of the general attempts to turn the clock many years back on the human rights front. &lt;/p&gt;
&lt;p&gt;I also want to make this all visible and say to anyone in this position that you&amp;#39;re not alone, and it&amp;#39;s not you. And you&amp;#39;re not imagining it. &lt;/p&gt;
&lt;p&gt;I could have listed some advice on how to be an ally - but I decided that this year, it&amp;#39;s about sharing my experiences and just painting the picture of what tech can look like for an outspoken, non-binary woman in tech. Also, my previous blog posts give tips on being better ally - you can find the blog posts as part of the series at the beginning and end of the blog post on Dev. &lt;/p&gt;
&lt;h2 id=&quot;video&quot;&gt;Video&lt;/h2&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/vPZChpXZqEs?si=7Mtw_taRzPaxhi8Y&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;</content
    >
  </entry>
   
  
  <entry>
    <title>Does Gemini Generate Accessible Android Apps?</title>
    <link href="https://eevis.codes/blog/2025-03-31/does-gemini-create-accessible-android-apps/" />
    <updated>2025-03-31T06:30:01.943Z</updated>
    <id>https://eevis.codes/blog/2025-03-31/does-gemini-create-accessible-android-apps/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/5UuFbMfu5jlri7HRHzoKCw/fdbfd45e680ce1d7eb08cb38da46e0ae/gemini-square.png"/>]]>
      &lt;p&gt;The AI is here, and we all are going to lose our jobs. Or that&amp;#39;s what I&amp;#39;ve been hearing for a while now, and I don&amp;#39;t believe in it. I&amp;#39;ve had this skepticism towards AI-generated code for many reasons, and the biggest of them all is accessibility.&lt;/p&gt;
&lt;p&gt;I don&amp;#39;t believe AI can create fully accessible applications just yet. Why? The training material is usually the code that is out there, and it is not accessible. I love to cite myself here, so if you want to find some stats about Android apps not being accessible, I wrote a master&amp;#39;s thesis on the topic: &lt;a href=&quot;https://osuva.uwasa.fi/handle/10024/17254&quot;&gt;Towards More Accessible Android Applications: An Actionable Accessibility Checklist for Android Developers&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;But as I don&amp;#39;t just want to state things without any backing on them, I decided to test if AI assistants can create accessible code. My hypothesis was that the code won&amp;#39;t be fully accessible, and it will mirror the apps developed by humans. &lt;/p&gt;
&lt;p&gt;This blog post is the first in a series of testing different AI assistants to create a small Android app, and how accessible it will turn out with each of them. I will start with Gemini, as it&amp;#39;s available via Android Studio, and I have easy access to it. In the next posts, I&amp;#39;ll try a couple of other tools to compare, and then I will write a summary post from all the tests. &lt;/p&gt;
&lt;h2 id=&quot;the-app&quot;&gt;The App&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s first look at the app that I&amp;#39;m testing. I did two rounds, and it was good that I did - the output changed quite a lot. &lt;/p&gt;
&lt;p&gt;I used stable Android Studio Meerkat for the first version, and at the time of writing this blog post, I realized I did not mark the exact version number. The second round was with Narwhal 2025.1.1 Canary 2. &lt;/p&gt;
&lt;h3 id=&quot;prompt&quot;&gt;Prompt&lt;/h3&gt;
&lt;p&gt;The prompt I gave to Gemini both times was: &lt;/p&gt;
&lt;blockquote class=&quot;noinline&quot;&gt;
&lt;p&gt;Can you generate UI screens for an app, 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;After Gemini was done with code generation, I also asked it to summarize how the app&amp;#39;s accessibility was considered. The answer for the first time was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Content Descriptions: All interactive elements (the OutlinedTextField acting as the dropdown trigger, the dropdown items, the save button) have appropriate contentDescription values for screen readers.&lt;/li&gt;
&lt;li&gt;Focus Management: The dropdown works correctly with keyboard and switch navigation.&lt;/li&gt;
&lt;li&gt;Keyboard Support: The dropdown can be opened and closed using the keyboard.&lt;/li&gt;
&lt;li&gt;Button: Button has focusable functionality.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;And, oh boy, did it have content descriptions and focusable buttons. But more about that later in the blog post. &lt;/p&gt;
&lt;p&gt;The second answer was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Semantic Structure and Content Grouping: The app uses Compose components in a way that establishes a clear UI hierarchy, making it easier for assistive technologies (like screen readers) to understand the relationships between elements and to navigate content.&lt;/li&gt;
&lt;li&gt;Text Alternatives and Content Descriptions: All non-text elements (icons, images) have descriptive text alternatives (contentDescription), and all visible text is sourced from string resources (stringResource), allowing screen readers to convey the meaning of these elements and making the app localizable.&lt;/li&gt;
&lt;li&gt;Keyboard and Voice Navigation Support: The app is designed to be fully navigable using a keyboard, and is compatible with voice navigation systems, thanks to the logical tab order and the clear labels provided for interactive elements.&lt;/li&gt;
&lt;li&gt;Visual Accessibility: The use of Material 3 ensures sufficient color contrast for users with low vision, and the use of sp units for text sizes allows users to adjust text scaling according to their preferences.&lt;/li&gt;
&lt;li&gt;Screen Reader Compatibility: Compose automatically generates an accessibility tree, and the app&amp;#39;s design uses components and modifiers correctly to make that tree accurate and helpful for screen readers, allowing users to easily understand and interact with all parts of the app.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The second answer was already more extensive and considered more aspects.&lt;/p&gt;
&lt;h3 id=&quot;the-ui&quot;&gt;The UI&lt;/h3&gt;
&lt;p&gt;Here&amp;#39;s a short video of how the app turned out, this is the second version:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/50hu8pjQsjj1jjgi93rofa/5d0975cb625b41b19a54b92323bc90a4/geminit-test-app.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;h2 id=&quot;testing-process&quot;&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 7 Pro, 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 id=&quot;the-things-i-caught-on-the-first-run&quot;&gt;The Things I Caught on the First Run&lt;/h2&gt;
&lt;p&gt;When testing the first version, I found problems. Oh boy, did I find problems. I&amp;#39;ll share about them next, explaining why they are problems. &lt;/p&gt;
&lt;h3 id=&quot;content-descriptions-everywhere&quot;&gt;Content Descriptions Everywhere&lt;/h3&gt;
&lt;p&gt;I first noticed that it added &lt;code&gt;contentDescription&lt;/code&gt;s pretty much everywhere because it said that they&amp;#39;re important for accessibility. &lt;/p&gt;
&lt;p&gt;While content descriptions are important for some elements, like non-decorative graphics, adding them to every single button and text (yes, it added that to text as well) makes the UI either annoying, or, in some cases, impossible to use with assistive technology. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s look at a couple of examples. &lt;/p&gt;
&lt;p&gt;First, the home screen. The code for each button looks like this (I&amp;#39;ve omitted the nonessential parts): &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Button(
    onClick = { .. },
    modifier = Modifier
        .semantics { 
            contentDescription = &amp;quot;View yarn stash&amp;quot; 
        }
        ...
) {
    Text(text = &amp;quot;View Yarn&amp;quot;, style = ...)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, what&amp;#39;s the problem here? When a screen reader or text-to-speech user arrives at these buttons, instead of &amp;quot;View Yarn&amp;quot;, they hear both the &amp;quot;View Yarn&amp;quot; and &amp;quot;View yarn stash&amp;quot;. The content description doesn&amp;#39;t give any new information; it&amp;#39;s just redundant repetition. If you&amp;#39;re navigating by listening, you most likely don&amp;#39;t want to hear anything redundant.  &lt;/p&gt;
&lt;p&gt;Then, there was the example of the yarn (and needle) detail screens. The code generated looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Text(
    text = &amp;quot;Brand: ${yarn.brand}&amp;quot;, 
    modifier = Modifier
        .fillMaxWidth()
        .semantics { 
            contentDescription = &amp;quot;Yarn brand&amp;quot;
        }
)
Text(
    text = &amp;quot;Name: ${yarn.name}&amp;quot;, 
    modifier = Modifier
        .fillMaxWidth()
        .semantics { 
            contentDescription = &amp;quot;Yarn name&amp;quot; 
        }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the previous example with the button, &lt;code&gt;contentDescription&lt;/code&gt; just added extra information to the button. With &lt;code&gt;Text&lt;/code&gt;-component, however, it overrides the contents of the text. So, instead of &amp;quot;Brand: Malabrigo, Name: Rios&amp;quot;, screen reader users would hear &amp;quot;Yarn brand, Yarn name&amp;quot;, which makes the screen practically useless for them. &lt;/p&gt;
&lt;h3 id=&quot;not-scrollable&quot;&gt;Not Scrollable&lt;/h3&gt;
&lt;p&gt;The next issue I ran into was that some screens don&amp;#39;t scroll if the content takes more height than the viewport. It becomes a problem fast with larger font sizes - if the page doesn&amp;#39;t scroll, and content takes more space than available, then the rest of the content is unreachable. &lt;/p&gt;
&lt;h3 id=&quot;clickable-and-focusable&quot;&gt;Clickable And Focusable&lt;/h3&gt;
&lt;p&gt;When I asked for a summary of the accessibility improvements, one of them was that the &amp;quot;Button has focusable functionality&amp;quot;. And yes, buttons should be focusable. And you know, if you&amp;#39;re using the native &lt;code&gt;Button&lt;/code&gt;-elements, they are focusable out of the box.&lt;/p&gt;
&lt;p&gt;But as we&amp;#39;re working with AI, it&amp;#39;s not that simple. The code for clickable elements (the needle and yarn list items) and buttons looks like this everywhere:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Card(
    modifier = Modifier
        ...
        .clickable { 
            onItemClick(needle.id)
        }
        ...
        .focusable()
) {
   ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Button(
    onClick = { ... },
    modifier = Modifier
        ...
        .focusable(),
) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you spot the problem? Both components are clickable (&lt;code&gt;Button&lt;/code&gt; has it under the hood, and the &lt;code&gt;Card&lt;/code&gt; has the &lt;code&gt;clickable&lt;/code&gt;-modifier), which already adds the element to the focus order. There is no need to add the &lt;code&gt;focusable&lt;/code&gt;-modifier.&lt;/p&gt;
&lt;p&gt;And if you&amp;#39;re wondering if there is no harm in adding it, from an accessibility perspective, there is. If you have both &lt;code&gt;clickable&lt;/code&gt; and &lt;code&gt;focusable&lt;/code&gt; modifiers, they both add a tab stop, which, for example, for someone navigating with a keyboard, means the following:&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;The expected behavior is that step 2 should not happen. &lt;/p&gt;
&lt;h2 id=&quot;the-second-run&quot;&gt;The Second Run&lt;/h2&gt;
&lt;p&gt;For the second test app, most problems were gone, but the buttons still had the text present twice. This time,  however, the reason was more understandable - A descriptive icon had the content description set, while it should be set to null, as the button already has a text.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Button(onClick = onShowYarnListClicked) {
    Icon(
        Icons.Filled.List, 
        stringResource(R.string.show_yarn_list)
    )
    Text(
        stringResource(R.string.show_yarn_list)
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&amp;#39;re wondering how to correctly add content descriptions, I&amp;#39;ve written a blog post about it: &lt;a href=&quot;http://eevis.codes/blog/2023-11-15/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs/&quot;&gt;How to Add Content Descriptions in Compose - A Guide for Android Devs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other than that, my tests didn&amp;#39;t find any significant issues that would entirely prevent the app&amp;#39;s usage for some groups of assistive technology users. However, I must note that I did a limited set of tests, and the app is rather simple, so this is not me saying everything&amp;#39;s accessible, but at least the big problems with the first version were gone.&lt;/p&gt;
&lt;h2 id=&quot;in-summary&quot;&gt;In Summary&lt;/h2&gt;
&lt;p&gt;After two rounds of testing with Gemini, I was surprised about the accessibility of the generated code. I mean, on the second run. The first one was pretty disastrous. &lt;/p&gt;
&lt;p&gt;What bothers me is that I don&amp;#39;t know why the quality improved so much for the second round. Was it luck? Was there some version change in Gemini? I tried looking for version mapping but did not come across any. So, if someone has any inside knowledge (or just a version mapping I missed), I&amp;#39;d love to hear!&lt;/p&gt;
&lt;p&gt;I&amp;#39;m interested in seeing how accessible code is generated by the other available AI tools. After testing with different tools, I will write a separate recap post and summarise my thoughts more. &lt;/p&gt;
&lt;p&gt;If you want to see the code for the second app, it&amp;#39;s available in Github: &lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/GeminiTestApp&quot;&gt;Gemini Test App&lt;/a&gt;.   &lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://osuva.uwasa.fi/handle/10024/17254&quot;&gt;Towards More Accessible Android Applications: An Actionable Accessibility Checklist for Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/GeminiTestApp&quot;&gt;Gemini Test App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://eevis.codes/blog/2023-11-15/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs/&quot;&gt;How to Add Content Descriptions in Compose - A Guide for Android Devs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>The Problem of Trun...</title>
    <link href="https://eevis.codes/blog/2025-04-26/the-problem-of-trun/" />
    <updated>2025-04-26T07:31:42.351Z</updated>
    <id>https://eevis.codes/blog/2025-04-26/the-problem-of-trun/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1mhaZzknJnfcHklOUAM8WQ/b783cafcb20c5fbab4cf466651035a89/the-problem-of-trun-square.png"/>]]>
      &lt;p&gt;Imagine that you&amp;#39;re in an unfamiliar location. You can see a signpost, but it&amp;#39;s old, and most of the letters of the words have been scratched off. The names in the signs all seem similar, given the couple of letters you can see. &lt;/p&gt;
&lt;p&gt;And you need to get somewhere, but the signpost is not helping at all. You&amp;#39;re lost. &lt;/p&gt;
&lt;p&gt;As the title gives away, I&amp;#39;m writing about something starting with &amp;quot;trun&amp;quot;. I could be writing about the problems of trunkfish. They seem a bit lost, so the topic could be that. Or I could be writing about truncheons and the issues caused by them. &lt;/p&gt;
&lt;p&gt;But no, this time, it&amp;#39;s about truncation, specifically when truncation has not been implemented correctly. &lt;/p&gt;
&lt;p&gt;Texts in applications are like those signs in a signpost. They most often lead to somewhere, give some instructions or necessary information, or are otherwise relevant. Texts wouldn&amp;#39;t be in the app if they weren&amp;#39;t meaningful.&lt;/p&gt;
&lt;h2 id=&quot;problem-of-truncation-done-wrong&quot;&gt;Problem of Truncation Done Wrong&lt;/h2&gt;
&lt;p&gt;We have this widely spread pattern on Android, where the words are truncated with an ellipsis if they don&amp;#39;t fit the container, without any way to expand the missing part of the text.&lt;/p&gt;
&lt;p&gt;For example, when you have a font size that is big enough, app names are truncated. The following image shows an example from accessibility settings and font size selection in Pixel:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/4UOOoi86idlVEVnz92qsv1/8eb49fbaf36a851acf84d6b3ea88580d/Screenshot_2025-04-09_at_13.28.50.png&quot; alt=&quot;Six apps with icons, names as follows: Ca..., Chr..., Drive, Maps, Mes..., and Sett..&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And okay, one could argue that if you install an app, you should be familiar with it and be able to recognize it even when you just get three letters from the name with the icon. &lt;/p&gt;
&lt;p&gt;But it&amp;#39;s not that straightforward - what if it&amp;#39;s an app you&amp;#39;ve used a year ago? Or what if you have memory issues due to something permanent or due to stress? Or any of the number of reasons why recognizing the app only with a partial name and an icon is not possible - let alone the cognitive load it creates. &lt;/p&gt;
&lt;p&gt;And it&amp;#39;s not just the app icons; it&amp;#39;s everywhere in the apps: The bottom navigation, buttons, links, titles... All these are places where the user would need the context of &amp;quot;What will happen if I press this item?&amp;quot; or &amp;quot;What is this section about?&amp;quot; or similar questions. &lt;/p&gt;
&lt;p&gt;The problem of truncation done wrong is about preventing access to information for users who use, for example, larger font sizes, smaller display sizes, or a combination that makes text truncate.&lt;/p&gt;
&lt;p&gt;And when we&amp;#39;re building apps, we&amp;#39;re thinking about the users, right? And building the apps for them?&lt;/p&gt;
&lt;h2 id=&quot;material-design-3-perspective&quot;&gt;Material Design 3 Perspective&lt;/h2&gt;
&lt;p&gt;But if this &amp;quot;truncate text with ellipsis and leave it at that&amp;quot; is so widespread pattern, it must be part of Material Design specifications, right? Let&amp;#39;s look at what Material Design 3 says about text and truncation. You can find all this information in &lt;a href=&quot;https://m3.material.io/foundations/writing/text-truncation&quot;&gt;Accessibility writing &amp;amp; text - Text truncation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The page referred to before mentions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Information should always be available to readers, even if text is truncated or wrapped.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If there&amp;#39;s an ellipsis, but no way to show the truncated text, it is not accessible&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There you have it. Even Material Design agrees with me - if there is no mechanism to see the text without truncation, it&amp;#39;s not accessible. And in this case, I would equate it to not being usable.&lt;/p&gt;
&lt;h2 id=&quot;so-whats-the-option&quot;&gt;So, What&amp;#39;s the Option?&lt;/h2&gt;
&lt;p&gt;Yes, the long text can still be a problem that needs to be solved somehow. So here are some options listed - first, some recommended ones, and then some I don&amp;#39;t recommend doing. &lt;/p&gt;
&lt;h3 id=&quot;dont-truncate-the-text-with-ellipsis&quot;&gt;Don&amp;#39;t Truncate the Text with Ellipsis&lt;/h3&gt;
&lt;p&gt;My first advice is - don&amp;#39;t truncate the text with ellipsis. Just let it take the space it needs and make your overall app UI responsive so that the text is not cut in any way. &lt;/p&gt;
&lt;p&gt;In many places, this strategy would work perfectly, and the only reason why it&amp;#39;s not used is that someone decides that &amp;quot;this text needs to be on one line, and the UI can&amp;#39;t be responsive&amp;quot;. The decision is often unconscious, just not realizing that not truncating the text is an option.&lt;/p&gt;
&lt;h3 id=&quot;add-expand-option&quot;&gt;Add &amp;quot;Expand&amp;quot;-option&lt;/h3&gt;
&lt;p&gt;There are cases when the whole text can&amp;#39;t be visible. One option for this case would be to add an &amp;quot;Expand&amp;quot; button, which then displays the whole text - either as a modal or by displaying the rest of the text by expanding it. This strategy is also recommended by Material 3. &lt;/p&gt;
&lt;h3 id=&quot;use-basicmarquee---but-make-it-accessible&quot;&gt;Use &lt;code&gt;basicMarquee&lt;/code&gt; - But Make It Accessible&lt;/h3&gt;
&lt;p&gt;What about the &lt;code&gt;basicMarquee&lt;/code&gt;-modifier? Wouldn&amp;#39;t it be a perfect way to display shorter texts which don&amp;#39;t fit on one line?&lt;/p&gt;
&lt;p&gt;Now, if you haven&amp;#39;t encountered the &lt;code&gt;basicMarquee&lt;/code&gt; modifier before, it works so that when a text is wider than its container, it scrolls horizontally, displaying the whole text that way. You can define how many times it does the scroll; the default is three. After iterations, it displays as much from the title as the container&amp;#39;s width allows, hiding the rest.&lt;/p&gt;
&lt;p&gt;Sounds great, right? For many, it is. But &lt;code&gt;basicMarquee&lt;/code&gt; doesn&amp;#39;t respect the &amp;quot;Remove animations&amp;quot;-setting. That can cause a lot of problems for folks who use that setting - for example, for me, these marquee-styled animations are the worst triggers for my symptoms.&lt;/p&gt;
&lt;p&gt;There are ways to make the &lt;code&gt;basicMarquee&lt;/code&gt; modifier more accessible, and I&amp;#39;ve written a blog post about that: &lt;a href=&quot;https://eevis.codes/blog/2025-02-20/making-basicmarquee-modifier-more-accessible/&quot;&gt;Making basicMarquee-Modifier More Accessible&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;non-recommended-ways&quot;&gt;Non-recommended Ways&lt;/h3&gt;
&lt;p&gt;There are also some ways to &amp;quot;solve&amp;quot; the problems of truncation that I don&amp;#39;t recommend. The first one is not to do anything, but as we care about our users, we&amp;#39;re not using that strategy, right?&lt;/p&gt;
&lt;p&gt;Another option would be preventing font scaling. While there might be some corner cases where preventing font scaling is acceptable, from an accessibility perspective, I&amp;#39;d say never do that. Users who use larger fonts use them for a reason, and if you prevent font scaling, it means that the text is way smaller than they can, for example, see. That, in turn, means that your app (or part of it) might become unusable for them. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;This blog post looked into the problem of truncation and how to solve the issues text truncation might present. I presented a couple of recommended ways and then two non-recommended ones. &lt;/p&gt;
&lt;p&gt;Have you run into problems with text truncation? &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://m3.material.io/foundations/writing/text-truncation&quot;&gt;Accessibility writing &amp;amp; text - Text truncation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2025-02-20/making-basicmarquee-modifier-more-accessible/&quot;&gt;Making basicMarquee-Modifier More Accessible&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Does Junie Create Accessible Android Apps?</title>
    <link href="https://eevis.codes/blog/2025-05-10/does-junie-create-accessible-android-apps/" />
    <updated>2025-05-10T06:37:02.018Z</updated>
    <id>https://eevis.codes/blog/2025-05-10/does-junie-create-accessible-android-apps/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2f0peUQLbM1IT9ObqkWcJ/3ea79d435c8bde12f058966e7050bd8f/junie-square.png"/>]]>
      &lt;p&gt;I&amp;#39;m continuing my tests with AI-generated Android code and how accessible these generated apps are. This time, the tool of choice is &lt;a href=&quot;https://www.jetbrains.com/junie/&quot;&gt;Junie&lt;/a&gt;, the coding agent by JetBrains.  &lt;/p&gt;
&lt;p&gt;If you want to know why I&amp;#39;m doing this or want to read my take on how accessible code Gemini creates, the first post is available at &lt;a href=&quot;https://eevis.codes/blog/2025-03-31/does-gemini-create-accessible-android-apps/&quot;&gt;Does Gemini Create Accessible Android Apps?&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;So, I got into the Early Access Program (EAP) before Junie was generally available and generated the first app. I had all these plans to proceed to the second run and write this blog post sooner, but then life happened, and I founded my own company. Suddenly, time passed, and Junie is now generally available. &lt;/p&gt;
&lt;p&gt;Before we dive into the application generation and accessibility testing, I&amp;#39;ll share a couple of words about Junie I wrote right after the first try, with some additions after the second run. &lt;/p&gt;
&lt;h2 id=&quot;junie&quot;&gt;Junie&lt;/h2&gt;
&lt;p&gt;As mentioned, I got access to EAP and was curious to try Junie out, as it differed from the AI solutions for coding I&amp;#39;d used before. Particularly, it also changed the files and didn&amp;#39;t just give me the code, and I could skip all the hard work of creating files and copy-pasting the code into those files.&lt;/p&gt;
&lt;p&gt;Some thoughts I had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I was impressed by the structured approach for doing things. It was easy to follow what was happening next and what was coming after that. &lt;/li&gt;
&lt;li&gt;When creating the first app, Junie kind of went overboard with adding libraries like Room into the project, but not using them in the code.&lt;/li&gt;
&lt;li&gt;With the first app, I had to solve one KSP version issue by hand, because I got tired of Junie trying to find a working library version. It used some non-existent version numbers first. The second app used &lt;code&gt;kapt&lt;/code&gt;, which resolved the version right away.&lt;/li&gt;
&lt;li&gt;I had to iterate a bit with the first version to get all the features I wanted, like adding yarn. The second app generation was smooth from this point of view.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-app&quot;&gt;The App&lt;/h2&gt;
&lt;p&gt;As with the tests with Gemini, I did two rounds of testing - first, with the Early Access Program version back at the beginning of April, and the second one was with the one available at the beginning of May.&lt;/p&gt;
&lt;p&gt;For these tests, I used IntelliJ Idea with plugins for Android development to use Junie, as it&amp;#39;s not available in Android Studio. &lt;/p&gt;
&lt;h3 id=&quot;prompt&quot;&gt;Prompt&lt;/h3&gt;
&lt;p&gt;The prompt I gave to Junie both times was: &lt;/p&gt;
&lt;blockquote class=&quot;noinline&quot;&gt;
&lt;p&gt;Can you generate UI screens for an app, 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;I also asked for a summary of how accessibility was considered. The answer from the first run disappeared into the void (because I messed up with saving it), but the second time, I was wise enough to save the document Junie had created. As a summary, it listed the following things:&lt;/p&gt;
&lt;blockquote class=&quot;noinline&quot;&gt;
&lt;p&gt; The Yarn Stash application demonstrates a strong commitment to accessibility through:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Comprehensive content descriptions for screen reader support&lt;/li&gt;
&lt;li&gt;Proper handling of nested content descriptions to avoid duplicate announcements&lt;/li&gt;
&lt;li&gt;Use of string resources for all text including accessibility labels&lt;/li&gt;
&lt;li&gt;Scalable typography that respects system font size settings&lt;/li&gt;
&lt;li&gt;Material Design components with built-in accessibility features&lt;/li&gt;
&lt;li&gt;Logical semantic structure for navigation&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to read the full summarization, it&amp;#39;s available in the &lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/JunieTestApp/ACCESSIBILITY.md&quot;&gt;ACCESSIBILITY.md&lt;/a&gt;-document in the repository. And just to be clear: These are words from Junie, and I disagree with many of them (we&amp;#39;re getting into it in a bit).&lt;/p&gt;
&lt;h3 id=&quot;the-ui&quot;&gt;The UI&lt;/h3&gt;
&lt;p&gt;Here&amp;#39;s a short video of how the app turned out; this is the version from the second run:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/7C9XM74CNobYJ4qXZZaRD8/30d4fc6e7637799890a25d841ca11805/junie-yarn-stash.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;h2 id=&quot;testing-process&quot;&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 7 Pro, 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 id=&quot;things-i-caught-on-the-first-run&quot;&gt;Things I Caught on the First Run&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;ve read my previous blog post about Gemini and accessible app generation, you already know some of the problems I will present. &lt;/p&gt;
&lt;h3 id=&quot;content-descriptions&quot;&gt;Content Descriptions&lt;/h3&gt;
&lt;p&gt;As Junie summarized for the second app, the code contained comprehensive content descriptions. As with Gemini, there were redundant content descriptions - like adding a content description to a button with a text, which means a screen reader user would hear both of the texts. &lt;/p&gt;
&lt;p&gt;If you want to know more about the problems with too many content descriptions, &lt;a href=&quot;https://eevis.codes/blog/2025-03-31/does-gemini-create-accessible-android-apps/&quot;&gt;Does Gemini Create Accessible Android Apps?&lt;/a&gt; explains that in depth. &lt;/p&gt;
&lt;h3 id=&quot;home-screen-not-scrollable&quot;&gt;Home Screen Not Scrollable&lt;/h3&gt;
&lt;p&gt;When  I changed the font size to the biggest, and the content took more height than the viewport height, the view wasn&amp;#39;t scrollable. This was also a problem with the Gemini-generated apps - and I&amp;#39;ll repeat here that if the page doesn&amp;#39;t scroll, and content takes more space than available, then the rest of the content is unreachable.&lt;/p&gt;
&lt;h3 id=&quot;larger-font-sizes-not-supported&quot;&gt;Larger Font Sizes Not Supported&lt;/h3&gt;
&lt;p&gt;The other problem with larger font sizes was that the layouts of the yarn and needle screens, as well as the home screen, didn&amp;#39;t adjust for the text taking more space. The two-column layout was fixed, so when the text took more space, it just crammed everything into the two-column layout, making it harder to read. &lt;/p&gt;
&lt;p&gt;The more accessible solution would be to let the content flow into a one-column layout when there&amp;#39;s not enough space for a two-column layout. So, in code, the initial implementation looks like this (I&amp;#39;ve omitted the non-essential parts): &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Row(...) {
    Text(
        text = label,
        modifier = Modifier.weight(1f)
    )
    Spacer(modifier = Modifier.width(16.dp)) 
    Text(
        text = value,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One way to solve the issue here would be to use &lt;code&gt;FlowRow&lt;/code&gt; instead of &lt;code&gt;Row&lt;/code&gt; - that would automatically let the content flow into the next row if there weren&amp;#39;t enough space. Of course, some other solutions could work better for more complex layouts and stricter design requirements.&lt;/p&gt;
&lt;p&gt;The solution with &lt;code&gt;FlowRow&lt;/code&gt; could look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;FlowRow(...) {
    Text(
        text = label,
        modifier = Modifier.weight(1f)
    )
    Spacer(modifier = Modifier.width(16.dp)) 
    Text(
        text = value,
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;and-on-the-second-run&quot;&gt;And On the Second Run&lt;/h2&gt;
&lt;p&gt;As mentioned, the second run happened about a month after the first. All the problems I listed for the first run were present, and there were some extra ones as well. I&amp;#39;ll discuss them next. &lt;/p&gt;
&lt;h3 id=&quot;keyboard-navigation-problems&quot;&gt;Keyboard Navigation Problems&lt;/h3&gt;
&lt;p&gt;On the second run, keyboard navigation testing revealed some additional problems. When creating or editing needles, the dropdown for needle type selection didn&amp;#39;t work with a keyboard. After some testing, it seems that the problem is a bug in &lt;code&gt;ExposedDropdownMenu&lt;/code&gt; and not the code Junie created. &lt;/p&gt;
&lt;p&gt;Another thing I noticed was that there&amp;#39;s an invisible extra tab stop in yarn and needle screens. After some investigation, it turns out that there is a floating action button, which is not visible - it&amp;#39;s probably hidden behind the bottom bar as the app is wrapped with a &lt;code&gt;Scaffold&lt;/code&gt; containing the bottom bar, and the floating action button is defined in a &lt;code&gt;Scaffold&lt;/code&gt; in each individual screen. &lt;/p&gt;
&lt;h3 id=&quot;incorrect-semantics&quot;&gt;Incorrect Semantics&lt;/h3&gt;
&lt;p&gt;When I asked Junie to improve the accessibility of the second app, it did do some good. For example, it added heading-semantics to headings, and it got it almost right. Most of the elements it added &lt;code&gt;heading()&lt;/code&gt;-semantics were indeed headings. But not all. &lt;/p&gt;
&lt;p&gt;It also added some redundant semantics - for example, the bottom bar items got the &lt;code&gt;Tab&lt;/code&gt; role and state description. While those are things you should add for custom components, bottom bar items already have these semantics built-in.  &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Compared to Gemini, and especially the second run with Gemini, Junie seemed to generate code that had more accessibility issues. Those issues are something I see in code written by developers, which makes sense, as the material used for training LLMs is real-world apps, so the issues get amplified. You can see it from the not-so-good ways of solving problems, like adding extra content descriptions when not needed. &lt;/p&gt;
&lt;p&gt;Testing these two AI tools for code generation has already started to paint a picture, but I want to do some more testing before writing conclusions. Next up, I&amp;#39;m going to try out Cursor.&lt;/p&gt;
&lt;p&gt;If you want to see the code for the second run with Junie, it&amp;#39;s available in Github: &lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/JunieTestApp&quot;&gt;Junie Test App&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jetbrains.com/junie/&quot;&gt;Junie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2025-03-31/does-gemini-create-accessible-android-apps/&quot;&gt;Does Gemini Create Accessible Android Apps?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/JunieTestApp/ACCESSIBILITY.md&quot;&gt;ACCESSIBILITY.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/JunieTestApp&quot;&gt;Junie Test App&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Does Cursor Generate Accessible Android Apps?</title>
    <link href="https://eevis.codes/blog/2025-06-16/does-cursor-generate-accessible-android-apps/" />
    <updated>2025-06-16T05:30:02.393Z</updated>
    <id>https://eevis.codes/blog/2025-06-16/does-cursor-generate-accessible-android-apps/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/6hHgiGzHTxj0QwOE6uiIWC/b5c3f8a6829fed21abc54042172cc7eb/cursor-square.png"/>]]>
      &lt;p&gt; I&amp;#39;ve been testing the accessibility of Android code created by different AI tools, and this time, it&amp;#39;s time to test Cursor. I know it&amp;#39;s not the most ideal tool for Android development, but I still wanted to test it. &lt;/p&gt;
&lt;p&gt;If you&amp;#39;ve read the previous posts in the series, you know the drill - I&amp;#39;ll generate the app code and then test it. There are links to the blog post at the top of this post if you want to refresh your memory or if you haven&amp;#39;t read the previous blog posts. In them, I also explain why I&amp;#39;m doing this.&lt;/p&gt;
&lt;p&gt;So, let&amp;#39;s get to the actual thing we&amp;#39;re here for. &lt;/p&gt;
&lt;h2 id=&quot;the-app&quot;&gt;The App&lt;/h2&gt;
&lt;p&gt;This time, I did only one round of tests. It&amp;#39;s not that I got lazy or anything - using Cursor was just so annoying that I decided to do only one test. &lt;/p&gt;
&lt;p&gt;I used a combination of Android Studio and Cursor editor to construct the app. I created the project with Android Studio, opened it in Cursor, and wrote the app prompt. Then I moved to AS, tried to build the project, fixed issues with dependencies, and built it again. Then I realized it didn&amp;#39;t have proper navigation, so I moved back to Cursor and did some additional prompting. After several similar rounds, I finally got the app to my phone and the testing phase. &lt;/p&gt;
&lt;p&gt;There were some good and not-so-great sides to using Cursor from an Android development point of view. The setup was smooth, but once I got to do some actual work with Android, the code had many problems. For example, it didn&amp;#39;t add dependencies at all, and I had to add them by hand. It also added some non-existent parameters to Composable functions. &lt;/p&gt;
&lt;p&gt;So yeah, compared to Gemini and Junie (tools I&amp;#39;ve tested so far), I would go with either of those for Android development instead of Cursor.&lt;/p&gt;
&lt;h3 id=&quot;prompt&quot;&gt;Prompt&lt;/h3&gt;
&lt;p&gt;The prompt I gave to Cursor was: &lt;/p&gt;
&lt;blockquote class=&quot;noinline&quot;&gt;
&lt;p&gt;Can you generate UI screens for an app, 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;I also asked for a summary of how accessibility was considered. Here&amp;#39;s a short version without every example it gave. &lt;/p&gt;
&lt;blockquote class=&quot;noinline&quot;&gt;
&lt;ol&gt;
&lt;li&gt;Content descriptions:
&lt;ul&gt;
&lt;li&gt;All interactive elements have descriptive content descriptions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Semantic Information
&lt;ul&gt;
&lt;li&gt;In detail screens, we use semantics modifier to provide additional context. This helps screen readers understand the relationship between labels and values in detail items.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Material 3 Design
&lt;ul&gt;
&lt;li&gt;Using Material 3 components ensures proper contrast ratios&lt;/li&gt;
&lt;li&gt;Surface colors and text colors follow Material 3 guidelines for readability&lt;/li&gt;
&lt;li&gt;Proper spacing and touch targets for interactive elements&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Navigation
&lt;ul&gt;
&lt;li&gt;Clear navigation patterns with back buttons&lt;/li&gt;
&lt;li&gt;Consistent navigation structure&lt;/li&gt;
&lt;li&gt;Auto-mirrored icons for RTL support&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Text Hierarchy
&lt;ul&gt;
&lt;li&gt;Using appropriate typography styles&lt;/li&gt;
&lt;li&gt;Clear visual hierarchy helps users understand the content structure&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Interactive Elements
&lt;ul&gt;
&lt;li&gt;All clickable areas are properly sized for touch targets&lt;/li&gt;
&lt;li&gt;Cards are used to group related information&lt;/li&gt;
&lt;li&gt;Clear visual feedback for interactive elements&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don&amp;#39;t agree with all of them being something that improves accessibility, as we&amp;#39;ll see later in the blog post, but most are legit things - both from accessibility and this app&amp;#39;s code point of view.&lt;/p&gt;
&lt;h3 id=&quot;the-ui&quot;&gt;The UI&lt;/h3&gt;
&lt;p&gt;Here&amp;#39;s a short video of how the app turned out:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/4uA5OVPRORfF8hq959croM/eefcae9c913897b025b650100a3c1f7f/Screen_recording_20250611_080049.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;h2 id=&quot;testing-process&quot;&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 id=&quot;the-problems-i-found&quot;&gt;The Problems I Found&lt;/h2&gt;
&lt;p&gt;Once again, there were some accessibility problems. Many of them were similar to what Gemini and Junie created. Let&amp;#39;s look at the accessibility problems more closely. &lt;/p&gt;
&lt;h3 id=&quot;content-descriptions-are-not-going-anywhere&quot;&gt;Content Descriptions Are Not Going Anywhere&lt;/h3&gt;
&lt;p&gt;As with Gemini and Junie, Cursor also added extra content descriptions. While in this case, the content descriptions were the same as the text content, grouping the text with &lt;code&gt;mergeDescendants&lt;/code&gt; would have accomplished the same result without adding two ways of going through the content: First as the content description, and then as separate texts.&lt;/p&gt;
&lt;p&gt;In practice, that means that the user with a screen reader first navigates through the following texts (content descriptions): &amp;quot;Brand: Brand A&amp;quot;, &amp;quot;Yarn name: Yarn 1,&amp;quot; and so forth. The next round would be &amp;quot;Brand&amp;quot;, &amp;quot;Brand A&amp;quot;, &amp;quot;Yarn name&amp;quot;, and &amp;quot;Yarn 1&amp;quot;, which adds redundant information to the screen. With this amount of information, it would probably be just annoying, but it would become frustrating with more details on screen. &lt;/p&gt;
&lt;p&gt;Let&amp;#39;s look at the code behind this problem. This code snippet is from the list screen for different yarns. I&amp;#39;ve omitted irrelevant parts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Column(
  modifier = Modifier
    .semantics { 
      contentDescription = &amp;quot;$label: $value&amp;quot;
    }
) {
  Text(
    text = label
  )
  Text(
    text = value
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Better code would be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Column(
  modifier = Modifier
    .semantics(mergeDescendants = true) {} &amp;lt;-- Change to this
) {
  Text(
    text = label
  )
  Text(
    text = value
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;scrolling-is-not-supported&quot;&gt;Scrolling is Not Supported&lt;/h3&gt;
&lt;p&gt;Increasing font size reveals that the screens don&amp;#39;t support scrolling if the content doesn&amp;#39;t fit into the view. That&amp;#39;s not optimal for the app, as its sole purpose is to list data. These problems become apparent when scaling font size up even sooner than with the default font size, as the content takes more space.&lt;/p&gt;
&lt;p&gt;Surprisingly, the app doesn&amp;#39;t prevent landscape orientation. However, as scrolling is not supported, every single screen is unusable, as there is less vertical space when orientation is horizontal. &lt;/p&gt;
&lt;p&gt;So, the solution here is to add scrollability to all screens - even those that don&amp;#39;t require it with the default font size most developers use when developing an app.&lt;/p&gt;
&lt;h3 id=&quot;font-scaling-causes-further-problems&quot;&gt;Font Scaling Causes Further Problems&lt;/h3&gt;
&lt;p&gt;When increasing the font size, fonts scale appropriately. However, as usual, the top app bar doesn&amp;#39;t stretch to accommodate a big font size with two lines of text. This is a common issue, and it&amp;#39;s more of a problem with the &lt;code&gt;TopAppBar&lt;/code&gt;-component&amp;#39;s default values.&lt;/p&gt;
&lt;p&gt;One way to fix the issue would be using &lt;code&gt;TopAppBar&lt;/code&gt;&amp;#39;s &lt;code&gt;expandedHeight&lt;/code&gt;-property together with different heights for smaller and bigger font scales. For this simple app, the straightforward solution could look like the following: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun NeedlesDetailScreen(...) {
    val fontScale = LocalConfiguration.current.fontScale
    val topAppBarHeight = if (fontScale &amp;lt; 1.5)
      TopAppBarDefaults.TopAppBarExpandedHeight
    else 
      TopAppBarDefaults.TopAppBarExpandedHeight * 1.5f
  }

  Scaffold(
    topBar = {
      TopAppBar(
      ....
        expandedHeight = topAppBarHeight
      )
    }
  ) {...}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This problem with &lt;code&gt;TopAppBar&lt;/code&gt; not scaling with bigger font sizes is not visible with every phone, so in general, this issue might be missed when testing, and thus, there are not many fixes for AI to learn from. For example, with this test app, I could see it with the Pixel 8 Pro emulator but not with the Pixel Fold I&amp;#39;m using. &lt;/p&gt;
&lt;h2 id=&quot;in-summary&quot;&gt;In Summary&lt;/h2&gt;
&lt;p&gt;While using Cursor was a pain, the code was somewhat ok. And when I say ok, I mean that it had the usual suspects, so, accessibility problems, but nothing major or additional. &lt;/p&gt;
&lt;p&gt;As I mentioned in the previous blog post, it&amp;#39;s already starting to paint a clear picture of the accessibility of AI-generated Android code. Still, I will do one more test with Claude Code before writing a summary post. &lt;/p&gt;
&lt;p&gt;If you want to see the app code, it&amp;#39;s available on Github: &lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/CursorTestApp&quot;&gt;Cursor Test App&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/CursorTestApp&quot;&gt;Cursor Test App&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>It&#39;s All About (Accessibility) Focus And Compose</title>
    <link href="https://eevis.codes/blog/2025-07-07/its-all-about-accessibility-focus-and-compose/" />
    <updated>2025-07-07T07:14:02.197Z</updated>
    <id>https://eevis.codes/blog/2025-07-07/its-all-about-accessibility-focus-and-compose/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/72ibO9tL0rI8vsxpbRuQd6/217d0cc03267572e32c788af6e26ecbf/focus-square.png"/>]]>
      &lt;p&gt;I&amp;#39;ve seen multiple questions in various Slack communities, Stack Overflow, and other places related to focus on Android and how it doesn&amp;#39;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&amp;#39;ll discuss the differences and what you can do in different cases. Let&amp;#39;s first look at what I mean when I talk about accessibility focus and keyboard focus.&lt;/p&gt;
&lt;h2 id=&quot;accessibility-focus&quot;&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&amp;#39;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&amp;#39;s an example from my phone:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1Wrg2nNHZVSAYixKVTI5FF/5e032ff39e7928a9052398922833240b/TalkBack.png&quot; alt=&quot;In the picture, there are two instances of the same UI element side by side: first, a button with the text &amp;#39;Button&amp;#39;, and below it, a switch with the label &amp;#39;Switch&amp;#39;. Under the first one, there&amp;#39;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.&quot; /&gt;&lt;/p&gt;
&lt;p&gt; It&amp;#39;s set by the system, so the app developer can&amp;#39;t edit it, and there shouldn&amp;#39;t be any need to do that either. &lt;/p&gt;
&lt;h2 id=&quot;keyboard-focus&quot;&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&amp;#39;s not that visible: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1G8xSRBFEy1pfNH6pp4uof/7b5ba4f4453774e273b1a24c88520577/Keyboard.png&quot; alt=&quot;In the picture, there are two instances of the same UI element side by side: first, a button with the text &amp;#39;Button&amp;#39;, and below it, a switch with the label &amp;#39;Switch&amp;#39;. Under the first one, there&amp;#39;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.&quot; /&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 id=&quot;making-a-component-focusable&quot;&gt;Making a Component Focusable&lt;/h2&gt;
&lt;p&gt;So you&amp;#39;re creating a custom component, and wondering if it&amp;#39;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&amp;#39;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&amp;#39;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&amp;#39;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 id=&quot;moving-focus-programmatically-to-a-component&quot;&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&amp;#39;s actions. For keyboard and keyboard-emulating navigation methods, this is relatively straightforward. For screen readers, it requires a little bit more. Let&amp;#39;s first discuss the keyboard focus and then the accessibility focus. &lt;/p&gt;
&lt;h3 id=&quot;keyboard-focus-1&quot;&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&amp;#39;s say, a button click, &lt;code&gt;FocusRequester&lt;/code&gt; is your friend.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s what using it looks like in code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun Component() {
  val focusRequester = remember { FocusRequester() }

  Button(
    onClick = { focusRequester.requestFocus() }
  ) { 
    Text(&amp;quot;Move focus&amp;quot;) 
  }

  CustomComponentWereFocusing(
    modifier = Modifier
      .focusRequester(focusRequester)
      .toggleable(...)
    )
}
&lt;/code&gt;&lt;/pre&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;&amp;#39;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&amp;#39;t work at all. &lt;/p&gt;
&lt;h3 id=&quot;accessibility-focus-1&quot;&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&amp;#39;t work here, so we need to resort to workarounds to accomplish it. I&amp;#39;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&amp;#39;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=&quot;https://slack-chats.kotlinlang.org/t/10509998/is-there-a-way-to-request-talkback-focus-on-a-particular-ele&quot;&gt;Kotlinlang Slack&amp;#39;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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Composable
fun Component() {
  val isFocused by remember { mutableStateOf(false) }

  LaunchedEffect(isFocused) {
    if (isFocused) isFocused = false
  }

  Button(
    onClick = { isFocused = true }
  ) { 
    Text(&amp;quot;Move focus&amp;quot;) 
  }

  CustomComponentWereFocusing(
    modifier = Modifier
      .semantics { focused = isFocused }
      .toggleable(...)
  )
}
&lt;/code&gt;&lt;/pre&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&amp;#39;s true, we set it back to false. It doesn&amp;#39;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 id=&quot;changing-traversal-order&quot;&gt;Changing Traversal Order&lt;/h2&gt;
&lt;p&gt;The final topic for this blog post is changing traversal order. Let&amp;#39;s say we have a layout with two rows and two columns. The layout looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/7FOzTyUmCVN09lxV6XR6NN/03d80ea352ca6eb47819f08ae3c9a410/Buttons.png&quot; alt=&quot;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.&quot; /&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&amp;#39;s discuss them separately, starting with accessibility focus. &lt;/p&gt;
&lt;h3 id=&quot;accessibility-focus-2&quot;&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&amp;#39;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&amp;#39;s to create a border for our changes in traversal order. You see, if we don&amp;#39;t set it, and set the &lt;code&gt;traversalIndex&lt;/code&gt; to the buttons, they&amp;#39;d be last in the screen&amp;#39;s focus order, because their traversal index is greater than 0. Zero is the default traversal index for elements that don&amp;#39;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&amp;#39;s focus order. &lt;/p&gt;
&lt;p&gt;So, in code it would look like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Column(
  modifier = Modifier
    .semantics {
      isTraversalGroup = true
    }
) {
    ...
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Row(...) {
    Button(
        modifier = Modifier.semantics { 
            traversalIndex = 1f
        },
        ... 
    ) { Text(&amp;quot;First&amp;quot;) }
    Button(
        modifier = Modifier.semantics { 
            traversalIndex = 3f
        },
        ... 
    ) { Text(&amp;quot;Third&amp;quot;) }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&amp;#39;ve omitted the other row, which contains the &amp;quot;Second&amp;quot; and &amp;quot;Fourth&amp;quot; buttons, from the example, but the idea remains the same. &lt;/p&gt;
&lt;p&gt;And that&amp;#39;s it. With these changes, the focus order for accessibility focus changes. Let&amp;#39;s talk about keyboard focus-related changes next.&lt;/p&gt;
&lt;h3 id=&quot;keyboard-focus-2&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val (first, second, third, fourth) = remember { 
  FocusRequester.createRefs() 
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Row(...) {
  Button(
    modifier = Modifier
      .focusRequester(first)
      .focusProperties { next = second},
    ... 
  ) { Text(&amp;quot;First&amp;quot;) }
  Button(
    modifier = Modifier
      .focusRequester(third)
      .focusProperties { next = fourth},
    ... 
  ) { Text(&amp;quot;Third&amp;quot;) }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&amp;#39;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 &amp;quot;First&amp;quot; button, then &amp;quot;Second&amp;quot;, then &amp;quot;Third&amp;quot;, and finally, &amp;quot;Fourth&amp;quot;. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, I&amp;#39;ve discussed the differences between focusing with screen readers (e.g., TalkBack) and navigating with the keyboard. I&amp;#39;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=&quot;https://developer.android.com/develop/ui/compose/accessibility/traversal&quot;&gt;Modify traversal order&lt;/a&gt; is the page to check for accessibility focus, and &lt;a href=&quot;https://developer.android.com/develop/ui/compose/touch-input/focus&quot;&gt;Focus in Compose&lt;/a&gt; summarizes the information about keyboard focus. &lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://slack-chats.kotlinlang.org/t/10509998/is-there-a-way-to-request-talkback-focus-on-a-particular-ele&quot;&gt;Kotlinlang Slack&amp;#39;s Compose channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/develop/ui/compose/accessibility/traversal&quot;&gt;Modify traversal order&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/develop/ui/compose/touch-input/focus&quot;&gt;Focus in Compose&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>10 Tips to Make Your Blog Posts More Accessible</title>
    <link href="https://eevis.codes/blog/2025-07-18/10-tips-to-make-your-blog-posts-more-accessible/" />
    <updated>2025-07-18T11:44:16.434Z</updated>
    <id>https://eevis.codes/blog/2025-07-18/10-tips-to-make-your-blog-posts-more-accessible/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3RRuY86C6JqttP4SaIrpU5/c6602a3ece5b559b01c490e40453febf/blog-a11y-square.png"/>]]>
      &lt;p&gt;Sometimes I encounter this weird idea that developers don&amp;#39;t need accessibility at all. Some people seem to believe that our development tools and resources don&amp;#39;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&amp;#39;re one of those people, you know, disabled software developers exist. You&amp;#39;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&amp;#39;ve been writing a blog since 2019, and as an accessibility specialist, I&amp;#39;ve tried to make my blog posts as accessible as possible. In this blog post, I decided to share some of the things I&amp;#39;ve learned over the years. So, without further ado, let&amp;#39;s get started. &lt;/p&gt;
&lt;h2 id=&quot;share-code-as-text-not-as-images&quot;&gt;Share Code as Text, not as Images&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;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&amp;#39;s super annoying to copy the code from a blog post if it&amp;#39;s an image. The reader would need to type it out, instead of just copying and pasting. At least for me, when  I&amp;#39;m sharing code in a blog post, it&amp;#39;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&amp;#39;s the text alternative part - if you&amp;#39;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&amp;#39;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&amp;#39;re unfamiliar with the concept, I&amp;#39;ll explain it in the next section.&lt;/p&gt;
&lt;h2 id=&quot;write-text-alternatives-for-graphics&quot;&gt;Write Text Alternatives for Graphics&lt;/h2&gt;
&lt;p&gt;The next tip I&amp;#39;m going to give is to write text alternatives for images. Usually, blogging platforms allow this, and if you&amp;#39;re using markdown, you can write it in the first brackets of the image. So, &lt;/p&gt;
&lt;p&gt;&lt;code&gt;![Alt text goes here](image url)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And if you&amp;#39;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;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img src=&amp;quot;...&amp;quot; alt=&amp;quot;Text alternative goes here&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why? Text alternatives are there for people who can&amp;#39;t see the picture. And that might include you, even if you&amp;#39;re a sighted person. Most importantly, it&amp;#39;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&amp;#39;s also useful in cases where the image doesn&amp;#39;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&amp;#39;s in text format, so it&amp;#39;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=&quot;https://webaim.org/techniques/alttext/&quot;&gt;WebAIM&amp;#39;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 id=&quot;use-readability-score-to-guide-your-writing&quot;&gt;Use Readability Score to Guide Your Writing&lt;/h2&gt;
&lt;p&gt;When you&amp;#39;re writing a blog post, I wholeheartedly recommend using tools like &lt;a href=&quot;https://hemingwayapp.com/&quot;&gt;Hemingway&lt;/a&gt; or &lt;a href=&quot;https://www.grammarly.com/&quot;&gt;Grammarly&lt;/a&gt; for grammar. Additionally, they&amp;#39;re great for getting a readability score for your text. I&amp;#39;ll share about it in a bit. &lt;/p&gt;
&lt;p&gt;I&amp;#39;m not a native English speaker, and I&amp;#39;ve relied on Grammarly for writing and checking my grammar for the whole time I&amp;#39;ve been writing blog posts. Nowadays, it complains less (I guess I&amp;#39;ve learned something, yay!), and it&amp;#39;s been extremely helpful in producing better text. And no, I&amp;#39;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&amp;#39;s degree. Okay, I really do hope you don&amp;#39;t think that, because not everyone does. &lt;/p&gt;
&lt;p&gt;We&amp;#39;re reading texts in different situations. If the text is written in a way that it&amp;#39;s readable for an eighth-grader, it also helps anyone who&amp;#39;s, e.g., under stress to understand the text better. And I don&amp;#39;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&amp;#39;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 id=&quot;list-links-at-the-end&quot;&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&amp;#39;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 &lt;a href=&quot;https://eevis.codes/blog/2025-07-18/10-tips-to-make-your-blog-posts-more-accessible/#links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/a&gt; section. &lt;/p&gt;
&lt;h2 id=&quot;add-table-of-contents&quot;&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&amp;#39;s automatically generated from the top-level headings. I&amp;#39;ve been thinking about copying it to the articles I&amp;#39;m cross-posting, but haven&amp;#39;t done so yet. &lt;/p&gt;
&lt;h2 id=&quot;write-clearly-and-simply&quot;&gt;Write Clearly and Simply&lt;/h2&gt;
&lt;p&gt;Writing clearly makes the blog post&amp;#39;s content more understandable. It&amp;#39;s also connected to the readability score, but it&amp;#39;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=&quot;https://webaim.org/techniques/writing/&quot;&gt;WebAIM: Writing Clearly and Simply&lt;/a&gt;. I&amp;#39;ll list the points from WebAIM below; you can read more about each step from the link to WebAIM&amp;#39;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&amp;#39;re finished, stop.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have to admit that this is something I&amp;#39;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 id=&quot;explain-code-examples&quot;&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&amp;#39;re never, for example, stressed or tired, explaining the code improves cognitive accessibility for readers.&lt;/p&gt;
&lt;h2 id=&quot;use-semantic-html&quot;&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&amp;#39;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&amp;#39;ve written a blog post waaaay back: &lt;a href=&quot;https://dev.to/eevajonnapanula/ode-to-semantic-html-38c3&quot;&gt;Ode to Semantic HTML&lt;/a&gt;. MDN&amp;#39;s Curriculum has also &lt;a href=&quot;https://developer.mozilla.org/en-US/curriculum/core/semantic-html/&quot;&gt;a section about Semantic HTML&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;display-estimated-reading-time&quot;&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&amp;#39;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;a href=&quot;https://www.npmjs.com/package/eleventy-plugin-time-to-read?activeTab=readme&quot;&gt;&lt;code&gt;eleventy-plugin-time-to-read&lt;/code&gt;&lt;/a&gt; for calculating the time to read. It&amp;#39;s not perfect, but it gives a good enough number for the purposes. &lt;/p&gt;
&lt;h2 id=&quot;consider-adding-a-summary&quot;&gt;Consider Adding a Summary&lt;/h2&gt;
&lt;p&gt;The last tip I&amp;#39;m going to share is to consider adding a short &amp;quot;Too Long, Didn&amp;#39;t Read&amp;quot; (TL;DR) type of summary at the beginning of the blog post. This summary provides the reader with an idea of what&amp;#39;s in the blog post, whether it&amp;#39;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&amp;#39;t gotten to it yet, as it has felt hard to summarize the essential points. Like, what&amp;#39;s the most important thing in the post? But I intend to explore how well different AI tools would do this, meaning I&amp;#39;ll just need to find the right prompt and then check the result to see if it&amp;#39;s correct.  &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this blog post, I&amp;#39;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&amp;#39;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 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://webaim.org/techniques/alttext/&quot;&gt;WebAIM&amp;#39;s Alt text page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hemingwayapp.com/&quot;&gt;Hemingway&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.grammarly.com/&quot;&gt;Grammarly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/eevajonnapanula/ode-to-semantic-html-38c3&quot;&gt;Ode to Semantic HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webaim.org/techniques/writing/&quot;&gt;WebAIM: Writing Clearly and Simply&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/curriculum/core/semantic-html/&quot;&gt;a section about Semantic HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/eleventy-plugin-time-to-read?activeTab=readme&quot;&gt;&lt;code&gt;eleventy-plugin-time-to-read&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Does Claude Generate Accessible Apps?</title>
    <link href="https://eevis.codes/blog/2025-08-22/does-claude-generate-accessible-apps/" />
    <updated>2025-08-22T10:08:32.560Z</updated>
    <id>https://eevis.codes/blog/2025-08-22/does-claude-generate-accessible-apps/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/7L4razn1yuOkeD0Jwh7DZn/01f2e417ceeab47cf37628f7e59c5784/claude-square.png"/>]]>
      &lt;p&gt;During the spring and summer, I&amp;#39;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&amp;#39;s first talk about the app I generated. &lt;/p&gt;
&lt;h2 id=&quot;the-app&quot;&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&amp;#39;t want to pay $20 for this one blog post, so I&amp;#39;m using the free version, which means I&amp;#39;ll need to operate within Claude&amp;#39;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 id=&quot;prompt&quot;&gt;Prompt&lt;/h3&gt;
&lt;p&gt;The prompt I gave to Claude was: &lt;/p&gt;
&lt;blockquote class=&quot;noinline&quot;&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&amp;#39;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=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/blob/main/ClaudeTestApp/app/src/main/java/com/eevajonna/claudetestapp/utils/AccessibilityExtensions.kt&quot;&gt;AccessibilityExtensions.kt&lt;/a&gt;-file, it&amp;#39;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&amp;#39;s mostly just a list without the actual implementation of the relevant points.&lt;/p&gt;
&lt;h3 id=&quot;the-ui&quot;&gt;The UI&lt;/h3&gt;
&lt;p&gt;Here&amp;#39;s a short video of how the app turned out:&lt;/p&gt;
&lt;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
 &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/7hmD3kRkfYBiGQzWKtXGRA/e2d81789d89d0417834655f943dee8a0/Screen_recording_20250810_065505.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&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 id=&quot;testing-process&quot;&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 id=&quot;the-problems-i-found&quot;&gt;The Problems I Found&lt;/h2&gt;
&lt;p&gt;As I expected, the app wasn&amp;#39;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&amp;#39;s look at the problems more closely.&lt;/p&gt;
&lt;h3 id=&quot;redundant-content-descriptions-are-here-to-stay-unfortunately&quot;&gt;Redundant Content Descriptions Are Here to Stay, Unfortunately&lt;/h3&gt;
&lt;p&gt;As with all the other apps, Claude&amp;#39;s app also had redundant content descriptions. Here&amp;#39;s one example (I&amp;#39;ve omitted the irrelevant parts)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;FloatingActionButton(
    ...
    modifier = Modifier
        ...
        .semantics {
            contentDescription = &amp;quot;Add new yarn to collection&amp;quot;
        }
) {
    Row(...) {
          ...
        Text(&amp;quot;Add New Yarn&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the &amp;quot;to collection&amp;quot; part doesn&amp;#39;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 &amp;quot;Add new yarn to collection. Add new yarn.&amp;quot; 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&amp;#39;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&amp;#39;s one example, a card for displaying needle list items with texts within the &lt;code&gt;Card&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Card(
    modifier = Modifier
        .semantics {
            role = Role.Button
            contentDescription = 
                &amp;quot;${needle.brandName} ${needle.type.displayName} &amp;quot; +
                &amp;quot;needles, ${needle.sizeMillimeters} millimeters. &amp;quot; + 
                &amp;quot;Tap for details.&amp;quot;
        },
    ...
) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The content description now contains all the same information as the contents of this card. It&amp;#39;s already exposed to accessibility services, so it&amp;#39;s redundant to add it here. However, it also adds the &amp;quot;Tap for details&amp;quot; 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&amp;#39;s no need to indicate it in the content description. In addition, as it&amp;#39;s an item on a list, so together with list semantics and clickability, there&amp;#39;s no need to indicate that tapping it would open the details. That&amp;#39;s a pattern in many apps, so it&amp;#39;s likely familiar to users. &lt;/p&gt;
&lt;p&gt;And now that we&amp;#39;re on the topic of &lt;code&gt;contentDescription&lt;/code&gt;s, it&amp;#39;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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;IconButton(
    onClick = { /* TODO: Edit yarn */ },
    modifier = Modifier.semantics {
        contentDescription = &amp;quot;Edit yarn details&amp;quot;
    }
) {
    Icon(
        painter = Icons.Default.Edit, 
        contentDescription = null
    )
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;IconButton(
    onClick = { /* TODO: Edit yarn */ }
) {
    Icon(
        painter = Icons.Default.Edit, 
        contentDescription = &amp;quot;Edit yarn details&amp;quot;
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;missing-grouping-in-details-screen&quot;&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 &amp;quot;Brand&amp;quot; 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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// DetailRow.kt

@Composable
fun DetailRow(...) {
    Row(
        modifier = Modifier
            .semantics(mergeDescendants = true) {}, &amp;lt;-- Adding this
        ...
    ) {
        Text(
            text = label,
            ...
        )
        Text(
            text = value,
            ...
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;hallucinated-semantics&quot;&gt;Hallucinated Semantics&lt;/h3&gt;
&lt;p&gt;Out of all the AI tools I&amp;#39;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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun Modifier.accessibleTextField(
    label: String,
    value: String,
    isRequired: Boolean = false
) = this.semantics {
    contentDescription = if (isRequired) 
        &amp;quot;$label, required field&amp;quot; 
    else 
        label
    text = AnnotatedString(value)
    role = Role.TextField &amp;lt;-- This role doesn&amp;#39;t exist
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When looking at the &lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/Role&quot;&gt;documentation of the &lt;code&gt;Role&lt;/code&gt;&lt;/a&gt;, it doesn&amp;#39;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 id=&quot;no-proper-support-for-button-navigation&quot;&gt;No Proper Support for Button Navigation&lt;/h3&gt;
&lt;p&gt;The final problem I&amp;#39;m going to discuss is that the app doesn&amp;#39;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 &lt;a href=&quot;https://eevis.codes/blog/2025-08-22/does-claude-generate-accessible-apps/#the-ui&quot;&gt;the UI-section&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When increasing the font and display sizes, the app doesn&amp;#39;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;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/51JbkuTErKVSBruOBAetK2/c35689cd931e09fd21b5a77ee180d62e/claude-test-app-navigation-bars.jpg&quot; alt=&quot;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.&quot; /&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 id=&quot;in-summary&quot;&gt;In Summary&lt;/h2&gt;
&lt;p&gt;As mentioned before, I liked Claude&amp;#39;s app the most from the UI perspective. From an accessibility perspective, the generated app wasn&amp;#39;t too bad - it did have accessibility issues, but not as many as, for example, Gemini&amp;#39;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&amp;#39;t actually used by the app, but it could have been.&lt;/p&gt;
&lt;p&gt;The results didn&amp;#39;t surprise me. I&amp;#39;ve reached the point of saturation, so it&amp;#39;s time to write the summary post for all the tests I&amp;#39;ve been running over the last few months. So, stay tuned, that&amp;#39;s coming next. &lt;/p&gt;
&lt;h2 id=&quot;links-in-blog-post&quot;&gt;Links in Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/ClaudeTestApp&quot;&gt;Claude Test App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eevajonnapanula/ai-app-generation-tests/blob/main/ClaudeTestApp/app/src/main/java/com/eevajonna/claudetestapp/utils/AccessibilityExtensions.kt&quot;&gt;AccessibilityExtensions.kt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/Role&quot;&gt;documentation of the &lt;code&gt;Role&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Wear OS Accessibility Considerations</title>
    <link href="https://eevis.codes/blog/2025-09-06/wear-os-accessibility-considerations/" />
    <updated>2025-09-06T07:27:30.641Z</updated>
    <id>https://eevis.codes/blog/2025-09-06/wear-os-accessibility-considerations/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/74iXpDPMmOJDdKMD47TWsd/50456672ed58a1b7fd4cf104583bfe6d/wear-os-a11y-square.png"/>]]>
      &lt;p&gt;Lately, I&amp;#39;ve been learning about Wear OS development. As I&amp;#39;m also an accessibility specialist, I&amp;#39;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&amp;#39;s been fascinating to dive into the topic and learn more about it. In this blog post, I&amp;#39;ll give pointers on Wear OS accessibility and share some of the learnings I&amp;#39;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&amp;#39;s dive in!&lt;/p&gt;
&lt;h2 id=&quot;screen-reader-seems-to-be-on-the-main-stage&quot;&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=&quot;https://developer.android.com/training/wearables/accessibility&quot;&gt;Accessibility on Wear OS&lt;/a&gt;-page first mentions font size and rotary input (which we&amp;#39;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&amp;#39;m not surprised - that&amp;#39;s the case with most of the accessibility documentation for any platform. And don&amp;#39;t get me wrong, screen reader accessibility is important, but there&amp;#39;s often this misconception that that&amp;#39;s all accessibility is. However, it&amp;#39;s not just screen readers - there are many other sides to it. &lt;/p&gt;
&lt;p&gt;In Wear OS&amp;#39;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&amp;#39;re misusing components, most of the things work.&lt;/p&gt;
&lt;p&gt;The &amp;quot;Optimize your app for Talkback&amp;quot; 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&amp;#39;re wondering what to write in a content description field, or if the element even needs one, I&amp;#39;ve written a guide: &lt;a href=&quot;https://eevis.codes/blog/2023-11-15/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs/&quot;&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=&quot;https://developer.android.com/training/wearables/accessibility&quot;&gt;Accessibility on Wear OS&lt;/a&gt;-page!&lt;/p&gt;
&lt;h2 id=&quot;small-screen-size&quot;&gt;Small Screen Size&lt;/h2&gt;
&lt;p&gt;As mentioned, small screen size brings its own accessibility (and usability) concerns. There&amp;#39;s not much space for elements on the screen, so many of the usual design patterns for, e.g., phone apps don&amp;#39;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&amp;#39;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&amp;#39;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 id=&quot;font-size-considerations&quot;&gt;Font Size Considerations&lt;/h2&gt;
&lt;p&gt;Then there are the font size considerations - your app needs to support user&amp;#39;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&amp;#39;re interested in why, I&amp;#39;ve written a blog post about it: &lt;a href=&quot;https://eevis.codes/blog/2025-04-26/the-problem-of-trun/&quot;&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&amp;#39;t fit the container.&lt;/p&gt;
&lt;h2 id=&quot;minimum-touch-target-size&quot;&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&amp;#39;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&amp;#39;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 id=&quot;different-input-types&quot;&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&amp;#39;ll discuss two input types next: The text and rotary input.&lt;/p&gt;
&lt;h3 id=&quot;text-input&quot;&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=&quot;https://developer.android.com/training/wearables/user-input/wear-ime&quot;&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&amp;#39;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&amp;#39;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 id=&quot;rotary-input&quot;&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;p&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;In short, from the accessibility perspective, supporting Rotary input is essential. The &lt;a href=&quot;https://developer.android.com/training/wearables/compose/rotary-input&quot;&gt;Rotary input with Compose&lt;/a&gt; page explains how you can do that, so I&amp;#39;m not covering it here.&lt;/p&gt;
&lt;h2 id=&quot;reduce-animations&quot;&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&amp;#39;t really need this setting on a watch as the animations that size don&amp;#39;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&amp;#39;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&amp;#39;re building something customized, or get a bug ticket from someone saying your app doesn&amp;#39;t respect the setting, here&amp;#39;s how you can read the value of the setting and then adjust your code according to it: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val isReduceMotionOn = LocalReduceMotion.current
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&amp;#39;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=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Android, Animations and Reduced Motion&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;magnification&quot;&gt;Magnification&lt;/h2&gt;
&lt;p&gt;Wear OS also provides a pretty neat magnification tool. While it&amp;#39;s not something you&amp;#39;d probably need to support in your app code, it&amp;#39;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&amp;#39;t pixelate too much when zoomed in. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;So, in this blog post I&amp;#39;ve discussed different accessibility considerations for Wear OS apps. I&amp;#39;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 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/training/wearables/accessibility&quot;&gt;Accessibility on Wear OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-11-15/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs/&quot;&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=&quot;https://eevis.codes/blog/2025-04-26/the-problem-of-trun/&quot;&gt;The Problem of Trun...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/training/wearables/compose/rotary-input&quot;&gt;Rotary input with Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/training/wearables/user-input/wear-ime&quot;&gt;Create input method editors on Wear&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-12-12/android-animations-and-reduced-motion/&quot;&gt;Android, Animations and Reduced Motion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>How to Work With Me?</title>
    <link href="https://eevis.codes/blog/2025-09-21/how-to-work-with-me/" />
    <updated>2025-09-21T10:26:27.982Z</updated>
    <id>https://eevis.codes/blog/2025-09-21/how-to-work-with-me/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2WGTbYgaGGm0iiNCS2t1bl/0890248d83f7fdb02699a0dc81c9f530/how-to-work-with-me-square.png"/>]]>
      &lt;p&gt;&lt;em&gt;This is a living document, and I try to update it when I learn or realize something new. I didn&amp;#39;t want to create a dedicated page for it, at least not just yet, so I published it as a blog post.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;about-me&quot;&gt;About me&lt;/h2&gt;
&lt;p&gt;Hey, I&amp;#39;m Eeva-Jonna, but everyone knows me as Eevis. I&amp;#39;m not Eeva, but I often let it slide with non-Finnish speakers, and two-part names aren&amp;#39;t that common elsewhere. But I&amp;#39;d love for you to use either Eevis or Eeva-Jonna, not Eeva.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m a non-binary woman, and my pronouns are they/she. In practice, it means that you can refer to me as &amp;#39;she&amp;#39; (mostly, because I&amp;#39;d be correcting everyone all the time if I wasn&amp;#39;t ok with &amp;#39;she&amp;#39;), but I often refer to myself as &amp;#39;they,&amp;#39; and I ask you to respect that. So if I write, for example, my bio with &amp;#39;they&amp;#39;, it&amp;#39;s not a typo, and you don&amp;#39;t need to fix it to &amp;#39;she&amp;#39;. &lt;/p&gt;
&lt;p&gt;I&amp;#39;m also autistic with a head injury, which means I process information differently than neurotypicals do. I try to explain some of these things in this document, but in general, please, don&amp;#39;t make assumptions through a neurotypical lens about my behaviour or the way I&amp;#39;m thinking. &lt;/p&gt;
&lt;p&gt;Lastly, I don&amp;#39;t do well with injustice, or any sexist, homophobic, transphobic, ableist, or racist comments or behaviours. I will bring them up &lt;a href=&quot;https://en.wikipedia.org/wiki/Spoon_theory&quot;&gt;if I have spoons&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;working-hours&quot;&gt;Working Hours&lt;/h2&gt;
&lt;p&gt;If I can choose my work hours without limitations when I&amp;#39;m part of a team, I work from about 7:30 to 15:30 Finnish time. Of course, I&amp;#39;m flexible if there are meetings or other things agreed, but these are my ideal working hours. For solo work, I usually start earlier. &lt;/p&gt;
&lt;p&gt;I&amp;#39;m definitely a morning person, and I do my best cognitive work in the morning. Writing is one of the things I reserve for mornings, as I&amp;#39;m most productive at that time. I can code all day, but I usually solve the most challenging tasks in the mornings. &lt;/p&gt;
&lt;h2 id=&quot;communication-style&quot;&gt;Communication Style&lt;/h2&gt;
&lt;p&gt;Written communication is most natural for me because it gives me time to think and formulate my thoughts. It also gives me time to process what the other person is telling me. &lt;/p&gt;
&lt;p&gt;Meetings, where I need to react quickly and give responses, are not my cup of tea most of the time. If I happen to have a lot of energy, I might shine in those meetings, but that&amp;#39;s like a maximum of 5 percent of the time. I&amp;#39;m often tired. &lt;/p&gt;
&lt;p&gt;I&amp;#39;m Finnish, which brings another layer to communication differences, especially in a multicultural environment. Even our friendly communication is often perceived as too direct compared to, for example, the US communication style. We tend to skip the small talk and fillers more often and go to the topic faster. We are also more reserved, which can often be interpreted as unfriendliness, but I can assure you, it&amp;#39;s not that.  &lt;/p&gt;
&lt;p&gt;Personally, I find small talk challenging, even though I&amp;#39;ve tried to learn how to do it. I don&amp;#39;t always even notice that there is a time for it, and I tend to go straight to the point, especially if the point is something I&amp;#39;m excited about. So if this happens, it&amp;#39;s not you, it&amp;#39;s me. &lt;/p&gt;
&lt;p&gt;And please, give me the honest opinion, situation, or prediction. For example, in a job interview, I appreciate a truthful explanation of the situation at the workplace, rather than some marketing lies. I really want to know what I&amp;#39;m committing to, even if it&amp;#39;s something suboptimal. The same goes for anything else - be it a project or other commitment. &lt;/p&gt;
&lt;h2 id=&quot;the-hard-conversations&quot;&gt;The Hard Conversations&lt;/h2&gt;
&lt;p&gt;When I bring up problems, I want to discuss them so that everyone gets heard, not just bring solutions. That often means that initially, I&amp;#39;m not providing solutions; I&amp;#39;m just describing the problem so that everyone knows what it&amp;#39;s about and can start ideating. &lt;/p&gt;
&lt;p&gt;I often have solution ideas in my mind, but I don&amp;#39;t want them to have the anchor effect. Instead, I want to hear others&amp;#39; ideas as well. In certain kinds of environments, this might look like I&amp;#39;m just bringing up problems, not solutions. &lt;/p&gt;
&lt;p&gt;Also, if I feel that the environment is not psychologically safe, for me, asking questions or help publicly is not easy, and it takes time to reach the point where I do it. &lt;/p&gt;
&lt;p&gt;One example is asking for help with a coding problem in a public Slack channel. Assume I constantly read messages where others are being borderline rude, and framing it as &amp;quot;we just give direct feedback&amp;quot;. In that case, it&amp;#39;s not a psychologically safe environment for anyone, but especially for someone who has had to endure more or less belittling for their whole career. So, when I&amp;#39;m writing the question on that channel, I need to mentally prepare for those rude comments and check all the possible scenarios for my problem, instead of, you know, just writing the question. And that takes a lot of spoons to do that. &lt;/p&gt;
&lt;h2 id=&quot;meetings&quot;&gt;Meetings&lt;/h2&gt;
&lt;p&gt;I&amp;#39;m one of those people who don&amp;#39;t enjoy meetings if there are too many of them in a day. Whether in-person or remote, I become exhausted after attending too many meetings in a day. I also need some time to transition from one meeting to another, so I can contribute my best in both meetings, especially if they&amp;#39;re unrelated. &lt;/p&gt;
&lt;p&gt;Meetings need to have an agenda. I need to know what I should be prepared for. If it&amp;#39;s an informal coffee meeting, that&amp;#39;s enough to know about it, but for meetings where something needs to be decided, I want to know beforehand what we&amp;#39;re doing and what the goal of the meeting is. &lt;/p&gt;
&lt;p&gt;If I&amp;#39;m in the middle of something, like solving a task or writing a document, asking me to just hop into a call is not a good idea if you want me to give my best in that meeting. So please, allow me at least 15 minutes to transition from my task if possible. &lt;/p&gt;
&lt;h2 id=&quot;information&quot;&gt;Information&lt;/h2&gt;
&lt;p&gt;When there is something new to understand, especially if it&amp;#39;s a big concept or a whole new codebase, my approach for getting into it might seem chaotic. I&amp;#39;m trying to absorb as much as possible, and then start building the bigger picture, piece by piece. &lt;/p&gt;
&lt;p&gt;So, the best way to give me information? Give me a summary, just enough to start working with, and let me ask questions, read the code or other materials, and absorb everything. &lt;/p&gt;
&lt;p&gt;When given a lot of information, I often don&amp;#39;t have questions right away. My brain needs a moment to build the connections before I can ask anything meaningful. So let me have some time, and then come back with questions. &lt;/p&gt;
&lt;h2 id=&quot;remote-vs-office&quot;&gt;Remote vs Office&lt;/h2&gt;
&lt;p&gt;When I come to the office, I can rarely concentrate there. So, office days are for human interaction, conversations, ideation, and other things that are easier in person. &lt;/p&gt;
&lt;p&gt;I do my best software development work at home, with my own setup. So, if a deadline is approaching or something else requires my concentration, I would rather skip the office days to get things done. &lt;/p&gt;
&lt;h2 id=&quot;the-things-often-misunderstood&quot;&gt;The Things Often Misunderstood&lt;/h2&gt;
&lt;p&gt;I gather a lot of information, and I start seeing patterns even before I realise it myself. So, when I bring concerns up, it&amp;#39;s not negativity for negativity&amp;#39;s sake. It&amp;#39;s me seeing risks and trying to address them early on, rather than having these &amp;quot;Oh, things went badly wrong&amp;quot;-meetings after the s*ht hits the fan. Trust me, I&amp;#39;ve been in many such meetings, after I&amp;#39;ve warned that the exact thing could happen. But my bringing up the risks is often misunderstood as pure negativity and resistance to change. &lt;/p&gt;
&lt;h2 id=&quot;when-im-dealing-with-stress&quot;&gt;When I&amp;#39;m Dealing with Stress&lt;/h2&gt;
&lt;p&gt;When I&amp;#39;m dealing with stress, I usually withdraw. I avoid coming to the office because I want to avoid any conflicts. I&amp;#39;m also likely to be sleeping poorly, so it takes a lot more from my social battery to meet with people, and this way, I&amp;#39;m trying to preserve my energy. &lt;/p&gt;
&lt;h2 id=&quot;the-things-i-struggle-with&quot;&gt;The Things I Struggle With&lt;/h2&gt;
&lt;p&gt;If things are too hazy, and I don&amp;#39;t even know who to ask for clarifications, I might struggle. And this is not really about the technical details. It&amp;#39;s about structure and understanding people and what they expect. &lt;/p&gt;
&lt;p&gt;In these cases, I don&amp;#39;t expect anyone to solve the issues for me. In the short term, I usually just need a nudge to find the relevant information to solve the problem at hand. If it&amp;#39;s a project, then I will need to understand the bigger picture and expectations to thrive. But if the project is a constant mess with constantly changing or not clear expectations, it is super hard for me, and I most likely burn out trying to exceed those unknown expectations.&lt;/p&gt;
&lt;h2 id=&quot;feedback&quot;&gt;Feedback&lt;/h2&gt;
&lt;p&gt;If you want to give me some constructive feedback, please allow me space to react to it. I usually don&amp;#39;t do well with it initially, and I need some time to process it. I am taking the relevant parts out of it, I promise, but I need time to get on terms with it. &lt;a href=&quot;https://en.wikipedia.org/wiki/Social_rejection#Rejection_sensitivity&quot;&gt;Rejection sensitivity&lt;/a&gt; is a real thing, folks. &lt;/p&gt;
&lt;p&gt;For positive feedback, it might seem that I don&amp;#39;t react to it. I&amp;#39;m learning to say thanks, but other times, I might just smile, and that&amp;#39;s it. It doesn&amp;#39;t mean I don&amp;#39;t appreciate it - it&amp;#39;s just that I haven&amp;#39;t learned how to react to it. &lt;/p&gt;
&lt;p&gt;I appreciate truthful feedback, so if there is nothing to say, then don&amp;#39;t say anything. I won&amp;#39;t, so if I give some (especially positive) feedback, you know I really mean it. &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Overall, behind most of these things, there&amp;#39;s the need to understand the &amp;quot;why&amp;quot; before I can actually do my best work. Sometimes it&amp;#39;s about the technical decisions, but very often, it&amp;#39;s about people&amp;#39;s motives. If I struggle to understand the why, I often struggle to complete the task at hand, because I don&amp;#39;t have all the necessary information.&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-document&quot;&gt;Links in the Document&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Spoon_theory&quot;&gt;if I have spoons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Social_rejection#Rejection_sensitivity&quot;&gt;Rejection sensitivity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Compose UI Tests for Larger Font Sizes: Scrolling and Text Truncation</title>
    <link href="https://eevis.codes/blog/2025-10-31/compose-ui-tests-for-larger-font-sizes-scrolling-and-text-truncation/" />
    <updated>2025-10-31T05:02:12.332Z</updated>
    <id>https://eevis.codes/blog/2025-10-31/compose-ui-tests-for-larger-font-sizes-scrolling-and-text-truncation/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3P7GA0LJiabZAgSEFwu64T/23d838f9692b215823630b6fe5298ac5/compose-tests-scrolling-and-truncation-square.png"/>]]>
      &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&amp;#39;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 id=&quot;the-screen-were-testing&quot;&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;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/2OsX5t1x6gS6jkp3dQHzdw/df8643e2a57fe98af59bd2b5c903a4f6/Screenshot_20251031_070425.png&quot; alt=&quot;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.&quot; class=&quot;portrait-img&quot; /&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=&quot;https://lgbtqia.fandom.com/wiki/LGBTQIA%2B_Wiki&quot;&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 id=&quot;testing-setup&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
class LargeFontSizeTests {
    @get:Rule val composeTestRule = createComposeRule()

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then create a function we can call in both of our tests to set content for testing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
fun setupScreen() {
    composeTestRule.setContent {
        AccessibilityTestsTheme {
            LargeFontScreen()
        }
    }
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
CompositionLocalProvider(
    LocalDensity provides Density(
        fontScale = 2f,
        density = LocalDensity.current.density
    ) 
) {
    LGBTQGlossaryScreen()
}
&lt;/code&gt;&lt;/pre&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 id=&quot;scrolling-content&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
@Test
fun contentIsScrolling() {
    setupScreen()

    composeTestRule
        .onNodeWithText(&amp;quot;Biromantic&amp;quot;)
        .assertIsNotDisplayed()

    composeTestRule.onNodeWithTag(&amp;quot;screen-content&amp;quot;)
        .assertIsDisplayed()
        .performTouchInput { swipeUp() }

    composeTestRule
        .onNodeWithText(&amp;quot;Biromantic&amp;quot;)
        .assertIsDisplayed()
}
&lt;/code&gt;&lt;/pre&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 id=&quot;truncated-text&quot;&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&amp;#39;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=&quot;https://eevis.codes/blog/2025-04-26/the-problem-of-trun/&quot;&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;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/1cTspWklPszt5jCTh0faNN/b60652d12ad8fa1c0f97e7f6184f0cf0/Screenshot_2025-10-28_at_6.47.44.png&quot; alt=&quot;A card with title Genderqueer and truncated text Non-binary generally is used as the...&quot; /&gt;&lt;/p&gt;
&lt;p&gt;and the full version looks like this: &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/6lxd7JoQGhxro11NRgT62A/f752fb7755e6350da856de5d859d96d3/Screenshot_2025-10-28_at_6.47.51.png&quot; alt=&quot;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.&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;helper-methods&quot;&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 id=&quot;semanticsnodeistruncatedwithellipsis&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
fun SemanticsNode.isTruncatedWithEllipsis(): Boolean {
    val textLayoutResult = mutableListOf&amp;lt;TextLayoutResult&amp;gt;()
    this.config
        .getOrNull(SemanticsActions.GetTextLayoutResult)
        ?.action
        ?.invoke(textLayoutResult)

    return textLayoutResult.firstOrNull { layout -&amp;gt;
        (0 until layout.lineCount).any {
            layout.isLineEllipsized(it)
        }

    } != null
}
&lt;/code&gt;&lt;/pre&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 id=&quot;isellipsized&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
fun isEllipsized(): SemanticsMatcher = SemanticsMatcher(
    &amp;quot;Truncated with Ellipsis&amp;quot;
) { node →
  node.isTruncatedWithEllipsis()
}
&lt;/code&gt;&lt;/pre&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 id=&quot;semanticsnodeinteractioncollectionperformclick&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
fun SemanticsNodeInteractionCollection.performClick() {
    this.fetchSemanticsNodes().forEachIndexed { index, _ -&amp;gt;
        this[index].performClick()
    }
}
&lt;/code&gt;&lt;/pre&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 id=&quot;the-actual-test&quot;&gt;The Actual Test&lt;/h3&gt;
&lt;p&gt;Now we have everything for writing the actual test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
@Test
fun textIsNotTruncatedWithEllipsis() {
    setupScreen()

    composeTestRule
        .onAllNodes(isEllipsized())
        .performClick()

    composeTestRule
        .onAllNodes(isEllipsized())
        .assertCountEquals(0)
}
&lt;/code&gt;&lt;/pre&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 id=&quot;wrapping-up&quot;&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 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://lgbtqia.fandom.com/wiki/LGBTQIA%2B_Wiki&quot;&gt;LGBTQIA+ Wiki ️‍️‍⚧️&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2025-04-26/the-problem-of-trun/&quot;&gt;The Problem of Trun..&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Does AI Generate Accessible Android Apps?</title>
    <link href="https://eevis.codes/blog/2025-11-25/does-ai-generate-accessible-android-apps/" />
    <updated>2025-11-25T03:19:04.973Z</updated>
    <id>https://eevis.codes/blog/2025-11-25/does-ai-generate-accessible-android-apps/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/6QmXXImUP0IyCKBsxvYs4b/fefd4ba8f649c84b4cf626fdae75ba90/does-ai-generate-accessible-android-apps-square.png"/>]]>
      &lt;p&gt;Over the past six months or so, I&amp;#39;ve been writing a series of blog posts in which I&amp;#39;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&amp;#39;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&amp;#39;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&amp;#39;re still relevant for learning purposes. &lt;/p&gt;
&lt;h2 id=&quot;redundant-content-descriptions&quot;&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 &amp;quot;Add yarn&amp;quot;, and the content description was &amp;quot;Add new yarn&amp;quot;, which adds zero new value. And in some cases, the implementation was such that the screen reader read both the text (&amp;quot;Add yarn&amp;quot;) and the content description (&amp;quot;Add new yarn&amp;quot;), 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 &amp;quot;Tap for details&amp;quot; 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: &amp;quot;Bla bla bla. Tap for details. Tap to activate&amp;quot;. And if you&amp;#39;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&amp;#39;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&amp;#39;s repeating these patterns.&lt;/p&gt;
&lt;h2 id=&quot;non-scrollable-screens&quot;&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&amp;#39;t contain that much information, so they didn&amp;#39;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&amp;#39;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&amp;#39;s understandable why this happens - the material the tools are trained with contains these issues.&lt;/p&gt;
&lt;h2 id=&quot;redundant-focusable-modifier&quot;&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&amp;#39;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 id=&quot;extra-tab-stops&quot;&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 id=&quot;incorrect-semantics&quot;&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&amp;#39;t actually exist. &lt;/p&gt;
&lt;h2 id=&quot;button-navigation-not-supported&quot;&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&amp;#39;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;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/51JbkuTErKVSBruOBAetK2/c35689cd931e09fd21b5a77ee180d62e/claude-test-app-navigation-bars.jpg&quot; alt=&quot;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.&quot; /&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&amp;#39;t test with button navigation, which is why this pattern is widespread. &lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&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, &amp;quot;YES&amp;quot; means that it&amp;#39;s a problem.&lt;/p&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;h2 id=&quot;wrapping-up&quot;&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&amp;#39;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&amp;#39;ve discussed are not that big. But it&amp;#39;s worth noting that the app built from the prompt itself isn&amp;#39;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&amp;#39;m not convinced just yet. Different AI-tools can be helpful, but they&amp;#39;re not yet ready to replace developers, as many non-developers would like to believe.&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Why I&#39;m starting a Newsletter About  Inclusive Android Apps</title>
    <link href="https://eevis.codes/blog/2025-12-02/why-im-starting-a-newsletter-about-inclusive-android-apps/" />
    <updated>2025-12-02T13:53:05.694Z</updated>
    <id>https://eevis.codes/blog/2025-12-02/why-im-starting-a-newsletter-about-inclusive-android-apps/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/2RQc0S3FlZY46Ju1ZtxbcP/f1cd656ee20c9d2651d073e1f03550f4/newsletter-blogpost.png"/>]]>
      &lt;p&gt;One thing I&amp;#39;m super excited about at the end of this year is a newsletter I&amp;#39;m launching: Inclusive Android Apps. It&amp;#39;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&amp;#39;m explaining why I&amp;#39;m starting it and what you can expect.&lt;/p&gt;
&lt;h2 id=&quot;why-inclusive-android-apps&quot;&gt;Why Inclusive Android Apps?&lt;/h2&gt;
&lt;p&gt;The short answer is that I&amp;#39;m starting the newsletter because I&amp;#39;d love to read it, and there&amp;#39;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&amp;#39;ve read my blog posts, seen me speak at events, or followed me on social media. I&amp;#39;m also queer, and many of the issues I&amp;#39;m discussing are from what I&amp;#39;ve either experienced or witnessed. However, I&amp;#39;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&amp;#39;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 id=&quot;what-to-expect-from-inclusive-android-apps&quot;&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 id=&quot;the-problem&quot;&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&amp;#39;s a problem. Depending on the problem, it might be more about code or about a design pattern. &lt;/p&gt;
&lt;h3 id=&quot;who-the-problem-hurts&quot;&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&amp;#39;ve been writing these issues and doing research, even I&amp;#39;ve been surprised by the number of different user groups affected by some of the problems. &lt;/p&gt;
&lt;h3 id=&quot;why-developers-do-it&quot;&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&amp;#39;s about not knowing specific solutions because there isn&amp;#39;t much material out there, and sometimes it&amp;#39;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 id=&quot;the-solution&quot;&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&amp;#39;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&amp;#39;t work for you.&lt;/p&gt;
&lt;h3 id=&quot;learn-more&quot;&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&amp;#39;s subject. Each link is curated, and I will write an explanation of why I selected it.&lt;/p&gt;
&lt;h2 id=&quot;subscribe-to-inclusive-android-apps&quot;&gt;Subscribe to Inclusive Android Apps&lt;/h2&gt;
&lt;p&gt;If you want to subscribe to the newsletter, here&amp;#39;s the link:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://eevis.codes/newsletter/&quot;&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;
</content
    >
  </entry>
   
  
  <entry>
    <title>The Problem of Rows Breaking with Large Text</title>
    <link href="https://eevis.codes/blog/2025-12-16/the-problem-of-rows-breaking-with-large-text/" />
    <updated>2025-12-16T03:39:14.982Z</updated>
    <id>https://eevis.codes/blog/2025-12-16/the-problem-of-rows-breaking-with-large-text/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/3bekHQnGTEkhtSlSYurxQF/c89fc24fa10be69fe404b90819324630/newsletter-1.png"/>]]>
      &lt;p&gt;This was originally sent as Issue #1 of my newsletter. &lt;a href=&quot;https://eevis.codes/newsletter/&quot;&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 id=&quot;the-problem-of-rows-breaking-with-large-text&quot;&gt;The Problem of Rows Breaking with Large Text&lt;/h2&gt;
&lt;p&gt;Let’s say we have this code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;Row(
  modifier = Modifier.fillMaxWidth(),
  verticalAlignment = Alignment.CenterVertically,
  horizontalArrangement = Arrangement.spacedBy(
    space = 16.dp,
    alignment = Alignment.CenterHorizontally
  )
) {
  Button(onClick = {}) {
    Icon(
      painter = painterResource(R.drawable.ic_arrow_back), 
      contentDescription = null
    )
    Text(&amp;quot;Previous year&amp;quot;)
  }
  Button(onClick = {}) {
    Text(&amp;quot;Next year&amp;quot;)
    Icon(
      painter = painterResource(R.drawable.ic_arrow), 
      contentDescription = null
    )
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which, in turn, looks like this with the default font size:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.buttondown.email/images/bd92cde5-f88a-42b5-acf0-549518de4f94.png?w=960&amp;fit=max&quot; alt=&quot;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.&quot; /&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;img src=&quot;https://assets.buttondown.email/images/8d2e46e4-8578-44b8-ad3a-1797c8fb02ae.png?w=960&amp;fit=max&quot; alt=&quot;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.&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;who-this-hurts&quot;&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 id=&quot;why-developers-do-this&quot;&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&amp;#39;t realize that &lt;code&gt;Row&lt;/code&gt; treats its children as a non-wrapping container, so if the content doesn&amp;#39;t fit, it just overflows or gets cut off. There&amp;#39;s no automatic wrapping behavior. It&amp;#39;s not a bug in &lt;code&gt;Row&lt;/code&gt;. It&amp;#39;s just not designed to handle dynamic content that might change size.&lt;/p&gt;
&lt;h3 id=&quot;the-solution&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;FlowRow( // Changed from Row
  modifier = Modifier.fillMaxWidth(),
  itemVerticalAlignment = Alignment.CenterVertically, // Changed from verticalAlignment
  verticalArrangement = Arrangement.spacedBy(8.dp), // NEW: spacing between wrapped rows
  horizontalArrangement = Arrangement.spacedBy(
    space = 16.dp,
    alignment = Alignment.CenterHorizontally
  )
) {
  Button(onClick = {}) {
    Icon(
      painter = painterResource(R.drawable.ic_arrow_back), 
      contentDescription = null
    )
    Text(&amp;quot;Previous year&amp;quot;)
  }
  Button(onClick = {}) {
    Text(&amp;quot;Next year&amp;quot;)
    Icon(
      painter = painterResource(R.drawable.ic_arrow), 
      contentDescription = null
    )
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which fixes the issues:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.buttondown.email/images/0c400d07-5bed-4ff3-b10d-9cddc8e076d2.png?w=960&amp;fit=max&quot; alt=&quot;Buttons are now aligned on top of each other, arrows and texts are both visible.&quot; /&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&amp;#39;t set &lt;code&gt;maxHeight&lt;/code&gt; or a fixed height, because if you do so, you&amp;#39;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&amp;#39;re on an older version, consider updating or using a custom wrapping layout.&lt;/p&gt;
&lt;h2 id=&quot;read-more&quot;&gt;Read More&lt;/h2&gt;
&lt;h3 id=&quot;flow-layouts-in-compose&quot;&gt;&lt;a href=&quot;https://developer.android.com/develop/ui/compose/layouts/flow&quot;&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 id=&quot;accessibility-scanner&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&quot;&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=&quot;https://www.youtube.com/watch?v=i1gMzQv0hWU&quot;&gt;Accessibility scanner - Accessibility on Android by Android Developers&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&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=&quot;https://eevis.codes/newsletter/&quot;&gt;Subscribe here&lt;/a&gt;. Next issue covers building inclusive gender forms.&lt;/p&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Year in Review - 2025 Edition</title>
    <link href="https://eevis.codes/blog/2025-12-31/year-in-review-2025-edition/" />
    <updated>2025-12-31T08:05:21.055Z</updated>
    <id>https://eevis.codes/blog/2025-12-31/year-in-review-2025-edition/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/1dZKc6Fjb9VWwNBbcvLd5B/85bdf4ff3b3c9833333ea3f9b6ced174/yir-2025-square.png"/>]]>
      &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=&quot;https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/?utm_source=yir-2025&quot;&gt;Year in Review - 2021 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-01-07/year-in-review-2022-edition/?utm_source=yir-2025&quot;&gt;Year in Review - 2022 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-12-29/year-in-review-2023-edition/?utm_source=yir-2025&quot;&gt;Year in Review - 2023 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-12-31/year-in-review-2024-edition/?utm_source=yir-2025&quot;&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 id=&quot;accomplishments&quot;&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&amp;#39; 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=&quot;https://eevis.codes/newsletter/&quot;&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 id=&quot;speaking&quot;&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;/li&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;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&amp;#39;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 id=&quot;writing&quot;&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 lang=&quot;”fi”&quot; href=&quot;https://selkodigital.fi/vieraskyna-verkkokauppojen-mobiilisovellusten-saavutettavuudessa-on-parantamisen-varaa/&quot;&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;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Be Mine and Add Interaction with Compose and Canvas&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2025-02-13&quot;&gt;February 13th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2025-02-13/be-mine-and-add-interaction-with-compose-and-canvas/?utm_source=yir-2025&quot;&gt;Read the post Be Mine and Add Interaction with Compose and Canvas&lt;/a&gt;
&lt;/article&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;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Echoes of the Past - Tech is Still Not Equal for All&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2025-03-10&quot;&gt;March 10th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2025-03-10/echoes-of-the-past-tech-is-still-not-equal-for-all/?utm_source=yir-2025&quot;&gt;Read the post Echoes of the Past - Tech is Still Not Equal for All&lt;/a&gt;
&lt;/article&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;article class=&quot;blog-embed&quot;&gt;
&lt;h3&gt;Wear OS Accessibility Considerations&lt;/h3&gt;
&lt;p&gt;Published at &lt;time datetime=&quot;2025-09-06&quot;&gt;September 6th&lt;/time&gt;&lt;/p&gt;
&lt;a href=&quot;https://eevis.codes/blog/2025-09-06/wear-os-accessibility-considerations/?utm_source=yir-2025&quot;&gt;Read the post Wear OS Accessibility Considerations&lt;/a&gt;
&lt;/article&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&amp;#39;s accessibility features as well. &lt;/p&gt;
&lt;h2 id=&quot;other-content-creation&quot;&gt;Other Content Creation&lt;/h2&gt;
&lt;p&gt;In the first quarter of 2025, I also started my own &lt;a href=&quot;https://www.youtube.com/@eeviscodes&quot;&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 id=&quot;the-bingo&quot;&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=&quot;https://eevis.codes/blog/2025-01-06/2025-please-be-gentle/&quot;&gt;2025, Please, Be Gentle&lt;/a&gt;, I created a bingo card or two for this year&amp;#39;s predictions. Let’s see how it turned out.&lt;/p&gt;
&lt;h3 id=&quot;the-good-stuff&quot;&gt;The Good Stuff&lt;/h3&gt;
&lt;p&gt;First, as a picture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/vlsviKx9DI2RFMLHKjxU4/6646ab08e59fb807758b48341f4ad832/2025_-_The_Good_Stuff_-_Whole_Year.png&quot; alt=&quot;2025 - The Good Stuff - Whole Year&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And then as a table:&lt;/p&gt;
&lt;div class=&quot;table-container&quot; tabIndex=&quot;0&quot;&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&#39;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&amp;#39;t get a bingo. &lt;/p&gt;
&lt;h3 id=&quot;the-more-realistic-one&quot;&gt;The More Realistic One&lt;/h3&gt;
&lt;p&gt;First, as a picture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/4MKtuRxpLQklltbnNUnqdQ/fbf2f0d7d9d1eae796c52dfaf118cd31/More_Realistic_2025_-_Whole_Year.png&quot; alt=&quot;More Realistic 2025 - Whole Year&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And then as a table:&lt;/p&gt;
&lt;div class=&quot;table-container&quot; tabIndex=&quot;0&quot;&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 &quot;intimidating&quot; for being assertive.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Someone interrupts when I&#39;m presenting&lt;/td&gt;
 &lt;td&gt;&lt;b&gt;Happened:&lt;/b&gt;&quot;Gender doesn&#39;t matter, just talent&quot;&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; &quot;You&#39;d be heard better, if you weren&#39;t so angry&quot;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Someone assumes I can&#39;t handle my tech setup&lt;/td&gt;
 &lt;td&gt;Meeting a new person who assumes I&#39;m not a developer&lt;/td&gt;
 &lt;td&gt;&lt;b&gt;Happened:&lt;/b&gt; &quot;I&#39;ve never seen anyone being discriminated&quot;&lt;/td&gt;
 &lt;td&gt;&lt;b&gt;Happened:&lt;/b&gt; &quot;Intent over impact&quot;&lt;/td&gt;
 &lt;td&gt;&lt;b&gt;Happened:&lt;/b&gt; Man needs to repeat what I&#39;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; &quot;Women just don&#39;t want to code&quot;&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;&quot;But they clearly didn&#39;t mean it&quot; &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;&quot;You were hired just for diversity quotas&quot;&lt;/td&gt;
 &lt;td&gt;&lt;b&gt;Happened:&lt;/b&gt; Everyone&#39;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; &quot;Equality has gone too far&quot;&lt;/td&gt;
 &lt;td&gt;&lt;b&gt;Happened:&lt;/b&gt; Hypothetical developer is referred to as &quot;he&quot; 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 id=&quot;other-things&quot;&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&amp;#39;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 id=&quot;what-about-2026&quot;&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 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/?utm_source=yir-2025&quot;&gt;Year in Review - 2021 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-01-07/year-in-review-2022-edition/?utm_source=yir-2025&quot;&gt;Year in Review - 2022 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2023-12-29/year-in-review-2023-edition/?utm_source=yir-2025&quot;&gt;Year in Review - 2023 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2024-12-31/year-in-review-2024-edition/?utm_source=yir-2025&quot;&gt;Year in Review - 2024 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/newsletter/&quot;&gt;Inclusive Android Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2025-01-06/2025-please-be-gentle/&quot;&gt;2025, Please, Be Gentle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2025-02-13/be-mine-and-add-interaction-with-compose-and-canvas/?utm_source=yir-2025&quot;&gt;Read the post Be Mine and Add Interaction with Compose and Canvas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2025-03-10/echoes-of-the-past-tech-is-still-not-equal-for-all/?utm_source=yir-2025&quot;&gt;Read the post Echoes of the Past - Tech is Still Not Equal for All&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eevis.codes/blog/2025-09-06/wear-os-accessibility-considerations/?utm_source=yir-2025&quot;&gt;Read the post Wear OS Accessibility Considerations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Beyond Font Scaling: Large Content Viewer with Compose</title>
    <link href="https://eevis.codes/blog/2026-01-17/beyond-font-scaling-large-content-viewer-with-compose/" />
    <updated>2026-01-17T06:00:04.914Z</updated>
    <id>https://eevis.codes/blog/2026-01-17/beyond-font-scaling-large-content-viewer-with-compose/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/6LmNI7aJyDj5jjVmJrA3cV/7d86c302ce2024ca4edf9b84dc461ea0/beyond-font-scaling-square.png"/>]]>
      &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 id=&quot;large-content-viewer-on-ios&quot;&gt;Large Content Viewer on iOS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/261/&quot;&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;img src=&quot;https://images.ctfassets.net/mpqufjsy02zr/25Fnsp4TplrDCI62hdKCxQ/04b170fa088feed2faccb2b399e9569e/Screenshot_2026-01-11_at_7.42.58.png&quot; alt=&quot;iOS&#39;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’.&quot; class=&quot;portrait-img&quot; /&gt;

&lt;h2 id=&quot;accessibility-considerations&quot;&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&amp;#39;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 id=&quot;building-navigation-bar-item-preview&quot;&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=&quot;https://gist.github.com/eevajonnapanula/94619c0862686af5b26c19a3bf0fa9fd&quot;&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;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/5JibWexIRKk6EWXS1u6e6c/3aa70e2ac67fbdf610499300e49dd7f0/preview-bottom-nav-bar-item.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&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 id=&quot;detect-the-long-press&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;var previewedItem by remember { mutableStateOf&amp;lt;NavItem?&amp;gt;(null) }
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
items.map { item -&amp;gt;
    val interactionSource = remember { MutableInteractionSource() }
    ...
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
val viewConfiguration = LocalViewConfiguration.current

// Long press duration

viewConfiguration.longPressTimeoutMillis
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
LaunchedEffect(interactionSource) {
    interactionSource.interactions.collectLatest { interaction -&amp;gt;
        when (interaction) {
            is PressInteraction.Press -&amp;gt; {
                delay(viewConfiguration.longPressTimeoutMillis)
                    previewedItem = item
                }
                is PressInteraction.Cancel -&amp;gt; {
                    previewedItem = null
                }
                is PressInteraction.Release -&amp;gt; {
                    selectedItem = item
                    previewedItem = null
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&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 id=&quot;display-the-preview&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
@Composable
private fun PreviewItem(
    item: NavItem,
    modifier: Modifier = Modifier
) {
    Column(
        modifier = modifier
            .clip(
                RoundedCornerShape(8.dp)
            )
            .background(NavigationBarDefaults.containerColor)
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Icon(
            painter = painterResource(item.selectedIcon),
            contentDescription = null,
        )
        Text(
            text = item.label
        )
    }
}
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
previewedItem?.let { item -&amp;gt;
    Box(modifier = Modifier.fillMaxSize()) {
        PreviewItem(
            modifier = Modifier
                .align(Alignment.Center),
            item = item
        )       
    }
}
&lt;/code&gt;&lt;/pre&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 id=&quot;enabling-the-preview&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
val fontScale = LocalDensity.current.fontScale
val previewEnabled by remember(fontScale) {
    mutableStateOf(fontScale &amp;gt; 1f)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, wrap the item preview with this boolean:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
if (previewEnabled) {
    previewedItem?.let { item -&amp;gt;
        ...
    }
}
&lt;/code&gt;&lt;/pre&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=&quot;https://gist.github.com/eevajonnapanula/94619c0862686af5b26c19a3bf0fa9fd&quot;&gt;The full code on GitHub Gist&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/261/&quot;&gt;Large content viewer&lt;/a&gt; &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://gist.github.com/eevajonnapanula/94619c0862686af5b26c19a3bf0fa9fd&quot;&gt;The full code on GitHub Gist&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>Adding Navigation support to Large Content Viewer with Compose</title>
    <link href="https://eevis.codes/blog/2026-02-28/adding-navigation-support-to-large-content-viewer-with-compose/" />
    <updated>2026-02-28T06:32:20.708Z</updated>
    <id>https://eevis.codes/blog/2026-02-28/adding-navigation-support-to-large-content-viewer-with-compose/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/4DF9E3ZdiXP4V0bSfsOMQZ/f99a42e5a3b8df19d3797e8ea14e472a/large-content-viewer-nav-support-square.png"/>]]>
      &lt;p&gt;In my previous blog post, &lt;a href=&quot;https://eevis.codes/blog/2026-01-17/beyond-font-scaling-large-content-viewer-with-compose/&quot;&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&amp;#39;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 id=&quot;keyboard-navigation-support&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
NavigationBarItem(
    modifier = Modifier
        .onFocusChanged {
            if (it.isFocused) {
                scope.launch {
                    delay(
                        viewConfiguration.longPressTimeoutMillis
                    )
                    previewedItem = item
                }
            } else {
                previewedItem = null
            }
        },

    ...

}
&lt;/code&gt;&lt;/pre&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;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/5tdOMUqKMCbm0MdpqekeXq/cd1edfc2361582416ca42be7574795e9/item-preview-keyboard.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;p&gt;Alright, we now have keyboard navigation support. Let’s talk about screen reader navigation next.&lt;/p&gt;
&lt;h2 id=&quot;screen-reader-navigation-support&quot;&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;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;
NavigationBarItem(
    modifier = Modifier
        ...
        .semantics {
            customActions = listOf(
                CustomAccessibilityAction(
                    label = &amp;quot;Preview item&amp;quot;,
                    action = {
                        scope.launch {
                            previewedItem = item
                        }
                        true
                    }
                )
            )
        },

    ...

}
&lt;/code&gt;&lt;/pre&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;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/6PtHczDgY6xVNIbewheizm/901a68773c7babde6f4a982b3a3a16cc/item-preview-talkback.mp4&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&gt;

&lt;h2 id=&quot;considerations-for-voice-access&quot;&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 id=&quot;wrapping-up&quot;&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&amp;#39;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=&quot;https://gist.github.com/eevajonnapanula/a6f26988aa81253c8917879347887202&quot;&gt;Github Gist&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;links-in-the-blog-post&quot;&gt;Links in the Blog Post&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://eevis.codes/blog/2026-01-17/beyond-font-scaling-large-content-viewer-with-compose/&quot;&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=&quot;https://gist.github.com/eevajonnapanula/a6f26988aa81253c8917879347887202&quot;&gt;Github Gist&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry>
   
  
  <entry>
    <title>About Invisibility, Propaganda, and Assumptions of Incompetence</title>
    <link href="https://eevis.codes/blog/2026-03-07/about-invisibility-propaganda-and-assumptions-of-incompetence/" />
    <updated>2026-03-07T14:03:23.161Z</updated>
    <id>https://eevis.codes/blog/2026-03-07/about-invisibility-propaganda-and-assumptions-of-incompetence/</id>
    <content
      type="html"
      >
      <![CDATA[<img src="https://images.ctfassets.net/mpqufjsy02zr/67Q2Da74fmConm7A6eOZg9/d684980a5aa51c28116fb2af11b0b311/iwd-26-square.png"/>]]>
      &lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href=&quot;https://dev.to/challenges/wecoded-2026&quot;&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=&quot;https://en.wikipedia.org/wiki/Minna_Canth&quot;&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 id=&quot;non-binary-woman-an-invisible-creature&quot;&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 id=&quot;what-if-your-existence-is-propaganda&quot;&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;video controls=&quot;&quot; class=&quot;portrait-video&quot;&gt;
  &lt;source src=&quot;https://videos.ctfassets.net/mpqufjsy02zr/6Iynd973tBuIfep12o7GjX/5acd0fd985153dcc8e31e88f2a8537b9/Screen_Recording_2025-10-23_at_13.41.43.mov&quot; type=&quot;video/mp4&quot; /&gt;  
&lt;/video&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=&quot;https://www.youtube.com/watch?v=stycBw0aa10&quot;&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;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ublefbbxf7c3dop9z2p.jpeg&quot; alt=&quot;Title: Comments and suggestions. Text: LGBT and non-binary propaganda. Didnt find out anything about Creative Coding.&quot; /&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 id=&quot;the-bad-ol-arguments&quot;&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&amp;#39;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 id=&quot;wrapping-up&quot;&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;
</content
    >
  </entry>
  
</feed>

