<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vinícius Veríssimo</title>
    <description>The latest articles on DEV Community by Vinícius Veríssimo (@vnicius).</description>
    <link>https://dev.to/vnicius</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F460084%2Fbe79850f-cf10-47c7-90a0-5721a6cffd57.jpg</url>
      <title>DEV Community: Vinícius Veríssimo</title>
      <link>https://dev.to/vnicius</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vnicius"/>
    <language>en</language>
    <item>
      <title>Our experience becoming a Compose-first app</title>
      <dc:creator>Vinícius Veríssimo</dc:creator>
      <pubDate>Sun, 29 Dec 2024 21:06:39 +0000</pubDate>
      <link>https://dev.to/vnicius/our-experience-becoming-a-compose-first-app-akk</link>
      <guid>https://dev.to/vnicius/our-experience-becoming-a-compose-first-app-akk</guid>
      <description>&lt;p&gt;This is a little piece about our journey applying Jetpack Compose to our existing codebase at the &lt;a href="https://play.google.com/store/apps/details?id=ai.moises" rel="noopener noreferrer"&gt;Moises.ai App&lt;/a&gt;, where we have time-based features that require high-performance and scalable UIs while also taking care of the developer's experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jetpack Compose Concerns
&lt;/h2&gt;

&lt;p&gt;Since we work with a small team of developers, we had many concerns about the impact of adding a new technology to our project. How would it impact the team's performance and the challenges of building new things and maintaining the current one? We had to start our explorations with the Jetpack Compose and still keep up with the high pace of a startup.&lt;/p&gt;

&lt;p&gt;We had to be cautious about fully relaying our solutions in a new way of building UIs. To do that, we had a few things in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It cannot decrease our lead time;&lt;/li&gt;
&lt;li&gt;It has to interoperate with our current codebase;&lt;/li&gt;
&lt;li&gt;We must be able to automatically test the UI with our current tools (at the time, it was &lt;a href="https://appium.io/docs/en/latest/" rel="noopener noreferrer"&gt;Appium&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;We can't impact our app's performance;&lt;/li&gt;
&lt;li&gt;The team needs to have time to build knowledge about it;&lt;/li&gt;
&lt;li&gt;The team must have the confidence to use it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that, we started to fill those gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploration Time
&lt;/h2&gt;

&lt;p&gt;We started by choosing someone from the team to spend part of their time exploring this new technology and how we should integrate it with our current code.&lt;/p&gt;

&lt;p&gt;This person had already studied Jetpack Compose using the &lt;a href="https://developer.android.com/courses/jetpack-compose/course" rel="noopener noreferrer"&gt;Android Developer Courses&lt;/a&gt; and had created a few small projects in Compose to practice. It was necessary to understand how it works and the difference from using Android Views.&lt;/p&gt;

&lt;p&gt;We realized that it wasn't just a matter of coding the views in different syntax; it was also necessary to rethink how we handle UI states to fully take advantage of the Compose UIs.&lt;/p&gt;

&lt;p&gt;With this in mind, it was time to implement and ship a small thing in Compose to get an idea of how it could be used in our project.&lt;/p&gt;

&lt;p&gt;Yes, we added Jetpack Compose to our codebase by writing new components with it and keeping the old ones untouched. Some people don't like this approach because it results in a codebase with mixed technologies, but we didn't want to risk changing thousands of code lines that were working fine to introduce a new way of doing things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Compose to the project
&lt;/h2&gt;

&lt;p&gt;In addition to the standard Gradle setup, we also have a project-specific requirement to use Compose in our features; it must use our Design System.&lt;/p&gt;

&lt;p&gt;We already had an implementation of our Design System in Android Views using a mix of components and styles. Since Compose handles themes, components, and styling differently, we have decided to re-implement part of our Design System in Compose.&lt;/p&gt;

&lt;p&gt;We prioritized the implementation of Typographies, Colors, and Buttons, the basic elements to build any small Proof of Concept. The first challenge is the initial attachment of the Compose structure to the Material Theme. The available set of colors and typographies wasn't enough for our Design System, so we implemented our own custom Theme inspired by the Material Theme implementation but focused on our necessities. It was hard work, but we took advantage of this soon.&lt;/p&gt;

&lt;p&gt;Finally, we were ready to add the Jetpack Compose to our main project, but we were still in the Proof of Concept state. We didn't want to add this new technology to our time-based features, which require high-performance UIs and complex business solutions.&lt;/p&gt;

&lt;p&gt;We decided to start with a static screen with simple UI elements to see how it will integrate with our current implementation. The app's navigation was Fragment-based, so we implemented a Fragment wrapper for the Compose UI, and we managed to keep the navigation between fragments. It worked pretty well.&lt;/p&gt;

&lt;p&gt;From the user perspective, there was no distinction between this new UI and the others. This achieves part of our interoperability criterion. I say part because we were essentially creating a whole screen in Compose without any mix of Compose and View on the same screen, but it was enough. Later, new requirements appeared, and we had the chance to use Compose only in part of an existing View screen, which we efficiently managed with &lt;a href="https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/compose-in-views" rel="noopener noreferrer"&gt;ComposeView&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With an old codebase, it is easy to have parts of the code that reflect patterns, ideas, and concepts of a given time. In a startup environment, the code constantly changes, evolves, and quickly adapts to the needs. Even though the current patterns we used to control our UI states weren't the best to take full advantage of the Compose capabilities.&lt;/p&gt;

&lt;p&gt;Our UI state control was mainly based on a few LiveDatas in ViewModels with different properties to be handled by the UI. As a better approach, we've decided to have the UI state as a Data class exposed by a Flow and collect it on the Compose side. This way, and using the concepts of the Unidirectional Data Flow, we managed to have more control over the UI state and provide it closer to the properties received by the composables. Later, we kept using it while maintaining some old view-based screens.&lt;/p&gt;

&lt;p&gt;To make it shippable, we still had to make it compatible with our UI test automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Validations Support
&lt;/h2&gt;

&lt;p&gt;For the automated test support, we used the &lt;a href="https://developer.android.com/develop/ui/compose/testing" rel="noopener noreferrer"&gt;testTag&lt;/a&gt; modifier to make the automation of our screen validations possible with Appium. Later, we used the same property when we moved to Espresso. So, we achieved another criterion.&lt;br&gt;
Okay, now we have a simple feature implemented with a testable new UI paradigm and architecture. Finally, it was time to ship it and see how it would perform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shaping the Compose muscle
&lt;/h2&gt;

&lt;p&gt;The final reception was excellent. The screen performed as intended and was seamless with the other screens, but it was lighter and had a few smooth animations. This is how we could differentiate the implementations of the final product.&lt;br&gt;
The team was satisfied with the result. The new feature was used as a proof of Concept for using Jetpack Compose in our platform and as a way to introduce Compose to the other team members.&lt;/p&gt;

&lt;p&gt;At the time, one person had more knowledge about Compose than the others. With the new technology approved, it was time to grow the team's knowledge about Compose.&lt;/p&gt;

&lt;p&gt;Because of the exploration moment, we had someone who knew all the paths to learn Compose. Then, there were months of mentoring, study, pairing, and building new features to practice composing in a real scenario with more dynamic screens with CRUD and reactive content.&lt;/p&gt;

&lt;p&gt;We had the opportunity to create more wrapped screens with Compose, partial Compose screens, and Compose screens with partial View content that wasn't yet supported in Compose. We tested new ways to organize components, files, and new supports on our Design System theme, rewiring exited components to fit our needs, highly based on the Material's patterns.&lt;/p&gt;

&lt;p&gt;More criteria were achieved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The team was happy to learn a new thing and to see it quickly being applied and shipped to production;&lt;/li&gt;
&lt;li&gt;The team was happy with Compose itself, how fast it built a new screen, listed things, created components, etc.&lt;/li&gt;
&lt;li&gt;We had the curve of being slower because we were trying to understand new concepts, new patterns, new small and big animations, and new ways to solve problems. However, once we were comfortable with Compose, we became faster and produced more code that was easier to read and understand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even though one criterion was missing, the one about using it in a feature that required high performance. Eventually, we had the opportunity to use our knowledge in a big feature with UI challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Challenges
&lt;/h2&gt;

&lt;p&gt;We received the request for a new feature:&lt;br&gt;
An audio recording screen that will render a waveform on the screen during the recording phase. Then, the user can seek the waveform, override any part of the recorded audio, play it, and process it.&lt;/p&gt;

&lt;p&gt;This was our first real big challenge in Compose. Long story short, we struggled to draw the waveform with Canvas in Compose in a performative way; we had many performance problems because the data kept changing fast, and we needed to draw the waveform smoothly for the user. We almost gave up using Compose to do it, but we were decided to make it work. In the end, we managed to reorganize the composables and structure the data to avoid unnecessary recompositions; we used the Layout Inspector and other available tools to evaluate our approaches.&lt;/p&gt;

&lt;p&gt;This challenge was necessary to achieve the last criterion; the screen performed great, and we were ready to do any other Compose challenge. It consolidated our ideas, patterns, and strategies.&lt;/p&gt;

&lt;p&gt;A few months later, we had more features that required performance and were time-based, but we were already sold to the Compose. We were already thinking of Compose-first solutions; using the old Views wasn't an option anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It takes time to adopt new technology in existing projects. In a startup environment, you can go fast and break many things or reduce your speed just enough to shape your ideas and have more substantial outcomes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhf3co7lwc3beynf2ifhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhf3co7lwc3beynf2ifhg.png" alt="Image description" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
We introduced the Jetpack Compose in baby steps. Even knowing this was the way to go, we needed to be sure that it would solve our needs, and we focused on spreading the knowledge across the team so everyone could work seamlessly in the code base.&lt;/p&gt;

</description>
      <category>android</category>
      <category>jetpack</category>
      <category>compose</category>
      <category>experience</category>
    </item>
    <item>
      <title>Rotary Input in Wear OS with Compose</title>
      <dc:creator>Vinícius Veríssimo</dc:creator>
      <pubDate>Tue, 15 Nov 2022 20:39:17 +0000</pubDate>
      <link>https://dev.to/vnicius/rotary-input-in-wear-os-with-compose-1pb5</link>
      <guid>https://dev.to/vnicius/rotary-input-in-wear-os-with-compose-1pb5</guid>
      <description>&lt;p&gt;The Rotary Input is one more way to the user interact with their wearables devices. You must handle this type of input since your users will expect this even unconsciously.&lt;/p&gt;

&lt;p&gt;Currently the users can perform the Rotary Input on smartwatches in 3 ways: crown, physical bezel, touch bezel. All of these option are transparent for us when handling the input, that is, for a simple handle of this input we don't need to know which one the user is using to interact with our app.&lt;/p&gt;

&lt;p&gt;So, let's see some examples of how to handle this with &lt;code&gt;ScalingLazyColumn&lt;/code&gt; and &lt;code&gt;Picker&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;💡 The following examples are using the version 1.1.0-rc01 of Composer for Wear OS&lt;/p&gt;

&lt;h2&gt;
  
  
  ScalingLazyColumn
&lt;/h2&gt;

&lt;p&gt;First of all, in order the handle the Rotary input, the component must have the focus. We can achieve this by manually requesting the focus to a component. So, let's create a instance of the FocusRequester:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;


&lt;p&gt;And then, set up this in our &lt;code&gt;ScalingLazyColumn&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;ScalingLazyColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.}&lt;/span&gt;
&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, we need to force the request of the focus.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestFocus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now our &lt;code&gt;ScalingLazyColumn&lt;/code&gt; will always have the focus. The handle is not done yet, now we need to actually react to the Rotary Input and scroll our list with it.&lt;/p&gt;

&lt;p&gt;First we need a instance of the &lt;code&gt;ScalingLazyListState&lt;/code&gt; and use the modifier &lt;code&gt;onRotaryScrollEvent&lt;/code&gt; to listen to the Rotary Inputs. Be aware that we need to perform this scroll with a Coroutine Scope.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;focusRequester&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;**{**&lt;/span&gt; &lt;span class="nc"&gt;FocusRequester&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;**}**&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;scrollState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberScalingLazyListState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;coroutineScope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberCoroutineScope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nc"&gt;ScalingLazyColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onRotaryScrollEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;coroutineScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;scrollState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scrollBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verticalScrollPixels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// it means that we are handling the event with this callback&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scrollState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.}&lt;/span&gt;
&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestFocus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's all. With a small configuration the &lt;code&gt;ScalingLazyColumn&lt;/code&gt; is reacting to the Rotary Input.&lt;/p&gt;
&lt;h2&gt;
  
  
  Picker
&lt;/h2&gt;

&lt;p&gt;For the &lt;code&gt;Picker&lt;/code&gt; component the setup will be basically the same:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pickerState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberPickerState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;initialNumberOfOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;initiallySelectedOption&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;focusRequester&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;FocusRequester&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;coroutineScope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberCoroutineScope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nc"&gt;Picker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pickerState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pickerState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onRotaryScrollEvent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;coroutineScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;pickerState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scrollBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verticalScrollPixels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="k"&gt;true&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestFocus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You notice some things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is no haptic feedback

&lt;ul&gt;
&lt;li&gt;If you test other apps that handle this input you will notice a haptic feedback during the scrolling, sadly this is not automatic, you need to do this manually or use the &lt;strong&gt;&lt;a href="https://google.github.io/horologist/"&gt;Horologist&lt;/a&gt;.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The Picker is not snapping to the selected item

&lt;ul&gt;
&lt;li&gt;With this approach sometimes the Picker doesn't snaps to the selected item, you can improve this with &lt;strong&gt;&lt;a href="https://google.github.io/horologist/"&gt;Horologist&lt;/a&gt;&lt;/strong&gt;, but by my test it isn't working 100%.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Rotary Input callbacks from the foundation library and Horologist are still in Experimental state, so soon they can change and be improved.&lt;/p&gt;
&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/2CzWz5Ad4iM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;You can check these and more example on my repository&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Vnicius"&gt;
        Vnicius
      &lt;/a&gt; / &lt;a href="https://github.com/Vnicius/rotary-input-samples"&gt;
        rotary-input-samples
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample codes showing the usage of Rotary input on Wear OS with Compose ⌚︎
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Rotary Input Samples&lt;/h1&gt;
&lt;p&gt;Sample codes showing the usage of Rotary input on Wear OS with Compose ⌚︎&lt;/p&gt;
&lt;h2&gt;
Samples&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Vnicius/rotary-input-samples./basic-rotary-input/"&gt;&lt;strong&gt;Basic Example&lt;/strong&gt;&lt;/a&gt; - This sample contains the basic handle of Rotart input with &lt;code&gt;ScalingLazyColumn&lt;/code&gt; and &lt;code&gt;Picker&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Vnicius/rotary-input-samples"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



</description>
      <category>android</category>
      <category>jetpackcompose</category>
      <category>compose</category>
      <category>wearos</category>
    </item>
    <item>
      <title>Criando um gerador automático de letras de Forró</title>
      <dc:creator>Vinícius Veríssimo</dc:creator>
      <pubDate>Sat, 07 Nov 2020 17:56:28 +0000</pubDate>
      <link>https://dev.to/vnicius/criando-um-gerador-automatico-de-letras-de-forro-59h5</link>
      <guid>https://dev.to/vnicius/criando-um-gerador-automatico-de-letras-de-forro-59h5</guid>
      <description>&lt;p&gt;Costumo dizer que o Forró é a trilha sonora do Nordeste, é realmente algo que está no ambiente. A maioria das músicas das bandas mais conhecidas eu nunca parei realmente para ouvir, mas conheço todas elas apenas por estar no Nordeste.&lt;/p&gt;

&lt;p&gt;Há uns meses a &lt;a href="https://twitter.com/dandaramcsousa" rel="noopener noreferrer"&gt;Dandara Sousa&lt;/a&gt; teve uma ideia (ao meu ver) genial de aplicar conceitos de análise de dados com músicas de Forró. Como? Ela decidiu utilizar os dados das músicas da banda Calcinha Preta numa série de posts (&lt;a href="https://dandaramcsousa.github.io/2020/05/26/dados-e-forro-vol-1.html" rel="noopener noreferrer"&gt;Vol. 1&lt;/a&gt;, &lt;a href="https://dandaramcsousa.github.io/2020/06/02/dados-e-forro-vol-2.html" rel="noopener noreferrer"&gt;Vol. 2&lt;/a&gt;, &lt;a href="https://dandaramcsousa.github.io/2020/06/11/dados-e-forro-vol-3.html" rel="noopener noreferrer"&gt;Vol. 3&lt;/a&gt;) e palestras.&lt;/p&gt;

&lt;p&gt;Daí me surgiu a ideia de também fazer algo utilizando o forró como contexto.&lt;/p&gt;

&lt;p&gt;No forró feito pela Calcinha Preta e muitas outras bandas, grupos e cantores, não só a melodia característica do gênero musical se destaca, mas também as letras que literalmente marcam gerações. Em sua maioria se resumem em desventuras amorosas e declarações de amor.&lt;/p&gt;

&lt;p&gt;Seria uma máquina capaz de escrever uma música de forró comparável aos grandes cancioneiros populares? É o que veremos a seguir.&lt;/p&gt;

&lt;h1&gt;
  
  
  Dados
&lt;/h1&gt;

&lt;p&gt;Primeiramente vamos aos dados.&lt;/p&gt;

&lt;p&gt;Irei utilizar as letras das músicas da &lt;a href="https://www.letras.mus.br/calcinha-preta/mais-tocadas.html" rel="noopener noreferrer"&gt;Calcinha Preta&lt;/a&gt;, &lt;a href="https://www.letras.mus.br/cavaleiros-do-forro/mais-tocadas.html" rel="noopener noreferrer"&gt;Cavaleiros do Forró&lt;/a&gt;, &lt;a href="https://www.letras.mus.br/limao-com-mel/mais-tocadas.html" rel="noopener noreferrer"&gt;Limão com Mel&lt;/a&gt; e &lt;a href="https://www.letras.mus.br/avioes-do-forro/mais-tocadas.html" rel="noopener noreferrer"&gt;Aviões do Forró&lt;/a&gt; disponíveis no &lt;a href="https://www.letras.mus.br/" rel="noopener noreferrer"&gt;letras.mus.br&lt;/a&gt;. Nada que um scraper não resolva.&lt;/p&gt;

&lt;p&gt;Foram um total de 2.309 músicas.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pré-processamento
&lt;/h1&gt;

&lt;p&gt;Para esse caso vou fazer um pré-processamento simples, apenas deixar tudo em minúsculo, separar as pontuações das palavras e colocar uma música por linha.&lt;/p&gt;

&lt;p&gt;Esse último passo do pré-processamento eu resolvi fazer assim pois num teste anterior utilizando um verso por linha eu não obtive um bom resultado. Acredito que seja pelos versos geralmente serem bem pequenos e com pouco contexto, além de ser muito comum que uma frase seja dividida em vários versos. Nesse caso decidi colocar todos os versos de cada música numa mesma linhas, mas separados por um token (&lt;code&gt;#&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Ou seja, o nosso algoritmo irá gerar a letra completa de uma música de uma só vez.&lt;/p&gt;

&lt;h1&gt;
  
  
  Treinando o algoritmo
&lt;/h1&gt;

&lt;p&gt;Para criar o modelo de geração de textos, eu vou utilizar a biblioteca &lt;a href="https://github.com/minimaxir/textgenrnn" rel="noopener noreferrer"&gt;textgenrnn&lt;/a&gt; que possibilita criar e treinar uma rede de geração de textos com Redes Neurais Recorrentes em poucas linhas de código.&lt;/p&gt;

&lt;p&gt;Por exemplo, treinei o modelo assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textgenrnn&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;textgenrnn&lt;/span&gt;

&lt;span class="n"&gt;textgen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;textgenrnn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;textgen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;textgen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train_from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_epochs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;gen_epochs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;word_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rnn_bidirectional&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;rnn_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dim_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Resultados
&lt;/h1&gt;

&lt;p&gt;Segue alguns exemplos de letras desse feat de Calcinha Preta, Cavaleiros do Forró, Limão com Mel e Aviões do Forró.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Eu não preciso de você
O que eu sinto por ti
É por favor, me faz enlouquecer
Toda segunda a me entregar
Me diga, sem me deixar, ah
Eu vou te dar o que você quiser
Eu não consigo te esquecer
Eu quero ver você falar de mim
Já cansei de esperar
Acabou nosso amor
E agora eu sofro por você
Mas eu não consigo esquecer
Você é linda e eu mais
Pra mim e não quer mais saber
De verdade
Você não vai me dominar
Você não vai me dominar
Você não é dona de mim para me dominar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Não sei o que fazer
Com tanta dor
Você não pode entender
Como a força desse amor
Vem fazer amor
Me dar calor
Me deixa louca
E me chama de ti
Digo sim pro meu coração
Isso tinha tudo que eu quis
Eu sei que te amo
Se ligue pra mim
Que nao da mais te esquecer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Você não sabe o que é amor
Me diz como é complicado
Mas não se amar ninguém é segurar
Eu amo você
Eu te amo demais
Eu não posso ficar
Eu te avisei
Eu te chamo
Quando me diz o que eu faço
Se você me vê
Agora eu me apaixonei
Por você toda vida
Eu queria ouvir você dizer
Te amo, mas
Mentir pra mim
Ainda é te amo ninguém
Hoje eu te amo, amo, amo
Sei que eu te amo, amo, amo
Amo, amo, amo...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Você é o meu amor
Só você me faz sentir tão sozinha
E agora que há 100 anos de viver
Eu não te esqueci
Nem sei o que fazer
Você me faz sofrer
Não vou deixar você fugir
Mas eu não posso te perder
Não posso te amar
Eu sei que vou ficar sozinho olhando pra você
Tenho muito medo que eu te dou
E não quis assim
Me diz o que é que eu faço pra ter você
Menina eu confesso: eu te amo demais!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Alguns dos versos são 100% "originais", ou seja, em nenhuma das letras tem um verso exatamente igual. Algumas vezes ele gera de 1 a 3 versos exatamente iguais a de alguma música, em outras ele gera 1 verso e meio de uma música e completa com outra música diferente.&lt;/p&gt;

&lt;p&gt;O que eu achei mais incrível é que com pouco treinamento ele já ser capaz de gerar versos e até mesmo estrofes com uma construção gramatical correta, original e com uma certa linha de contexto.&lt;/p&gt;

&lt;p&gt;Todos os códigos que eu utilizei estão nesse repositório.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Vnicius" rel="noopener noreferrer"&gt;
        Vnicius
      &lt;/a&gt; / &lt;a href="https://github.com/Vnicius/lyrics-generator" rel="noopener noreferrer"&gt;
        lyrics-generator
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Generate lyrics with textgenrnn
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>nlp</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>music</category>
    </item>
  </channel>
</rss>
