<?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: Zarah Dominguez</title>
    <description>The latest articles on DEV Community by Zarah Dominguez (@zmdominguez).</description>
    <link>https://dev.to/zmdominguez</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%2F139208%2Fb83b68c8-acf2-4675-9c3c-b7711fe33d90.jpeg</url>
      <title>DEV Community: Zarah Dominguez</title>
      <link>https://dev.to/zmdominguez</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zmdominguez"/>
    <language>en</language>
    <item>
      <title>Seeing What Talkback Sees 🔍</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Mon, 13 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/seeing-what-talkback-sees-2180</link>
      <guid>https://dev.to/zmdominguez/seeing-what-talkback-sees-2180</guid>
      <description>&lt;p&gt;One of the things we should be doing as Android developers is to ensure that our apps are as accessible as possible. There are a bunch of talks and articles that discuss the &lt;a href="https://youtu.be/nTNwZXVRGdY"&gt;motivations behind current MDC a11y support&lt;/a&gt;, the &lt;a href="https://youtu.be/bTodlNvQGfY"&gt;basic steps&lt;/a&gt; to &lt;a href="https://youtu.be/1by5J7c5Vz4"&gt;support a11y&lt;/a&gt;, &lt;a href="https://developer.android.com/guide/topics/ui/accessibility/testing"&gt;testing overviews&lt;/a&gt;, even &lt;a href="https://developer.android.com/guide/topics/ui/accessibility/service"&gt;creating your own a11y service&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;There’s a lot of resources that tell me what I should do, but what I found sorely lacking is information on helping me figure out what to do when something goes wrong (and knowing me, something is always bound to go wrong).&lt;/p&gt;

&lt;p&gt;For example, this is the upper part of my app’s homepage.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/xvM4iA2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r4pUcJjh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/xvM4iA2.png" title="Partial screenshot with a cart icon highlighted"&gt;&lt;/a&gt;&lt;small&gt;Talkback says "2 items in cart"&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;When the cart action menu item is focused, we expect Talkback to announce how many items are currently in the cart. However I noticed that sometimes Talkback just out of the blue says the number (and just the number) after announcing “2 items in cart”. Weird!&lt;/p&gt;

&lt;p&gt;If only I could dive into what Talkback “sees” so I could figure out how to fix the problem and make our Talkback announcements less confusing. I haven’t found any mention of how to do this in the official Android docs, and it is by sheer luck that I stumbled &lt;a href="https://withintent.uncorkedstudios.com/tutorial-debugging-android-accessibility-818cfd361414"&gt;upon this ✨ amazing ✨ article&lt;/a&gt; by &lt;a href="https://medium.com/@midori.bowen"&gt;Midori Bowen&lt;/a&gt; from 2018(!).&lt;/p&gt;
&lt;h3&gt;
  
  
  Wait, what! 😻
&lt;/h3&gt;

&lt;p&gt;It turns out that deep in the bowels of Talkback’s developer settings is an option to “Enable node tree debugging”. Midori links to the Android documentation on enabling this setting but that page has since been deleted. 😿&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/1g9EfIG"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2GXvN8ud--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/1g9EfIG.png" title="Screenshot of a screen showing a grid of items"&gt;&lt;/a&gt;&lt;small&gt;Turn it on! (While you're there, turn on "Display speech output" as well if you prefer. This will put up a &lt;code&gt;Toast&lt;/code&gt; of the Talkback announcements)&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;The “node tree” being referred to here is basically how Talkback interprets your view hierarchy. Having visibility on this would surely give us a lot of insight into what is going on under the hood.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://withintent.uncorkedstudios.com/tutorial-debugging-android-accessibility-818cfd361414"&gt;Follow the steps&lt;/a&gt; outlined in the OG post to enable node tree debugging. Some things have changed in Android and in Talkback since Midori’s post, but in general the steps in there should give you an idea of how to enable logging. For instance, instead of looking for “Unassigned”, assignable gestures are now subtitled “Tap to assign”. On some devices, Talkback allows multi-finger gestures, so there’s a lot of options to use to trigger node tree log dumps. If a gesture already has an action, you can still overwrite it if you wish to do so.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/ZyblUEq"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JLkNxL61--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ZyblUEq.png" title="Screenshots showing how to change Talkback gestures"&gt;&lt;/a&gt;&lt;small&gt;I settled on "Tap with 3 fingers"&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;
&lt;h3&gt;
  
  
  What do we have here? 🤔
&lt;/h3&gt;

&lt;p&gt;We can now trigger a dump of the node tree on any screen by using the gesture we have set in Talkback.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/XUMTJdg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NAn6CRwu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/XUMTJdg.png" title="Screenshot showing Talkback debugging gesture"&gt;&lt;/a&gt;&lt;small&gt;Talkback will tell you it has been done&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;At this point I want to reiterate to please do not be like me and spend an hour looking for where the logs actually are (I forgot that I have Logcat filters on 🤦‍♀️). They &lt;em&gt;are&lt;/em&gt; in Logcat, with the tag &lt;code&gt;TreeDebug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here’s the partial output of the node tree (timestamps remove for verbosity):&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The first few lines (lines 2-9) pertain to the status bar stuff, so let’s just ignore that. Our application’s contents start at line 10 (&lt;code&gt;type=TYPE_APPLICATION&lt;/code&gt;) with all the views on the screen in the following lines. Each &lt;code&gt;ViewGroup&lt;/code&gt; is tabbed which is really helpful in figuring out how each node maps to the view hierarchy. There’s a lot of information here and some things have changed since Midori’s post, so I thought it would be good to review what we can see in the logs. Let’s take line 18 for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(1100966)652.Switch:(668, 225 - 800, 357):CONTENT{See only Specials}:STATE{OFF}:not checked(action:FOCUS/A11Y_FOCUS/CLICK):focusable:clickable

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

&lt;/div&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(1100966)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The node’s hashcode (which the &lt;a href="https://github.com/google/talkback/blob/f5d564fdc915a74d8cde4868608f307de9ccf957/utils/src/main/java/com/google/android/accessibility/utils/TreeDebug.java#L75"&gt;Talkback source code&lt;/a&gt; refers to as a “poor man’s ID”)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;652&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The window ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Switch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The node’s class name (usually type of widget)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;invisible&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;This is not shown in this particular line – it is appended only if the view is invisible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(668, 225 - 800, 357)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Coordinates of the view on the screen, &lt;code&gt;(Left, Top - Right, Bottom)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TEXT{xxx}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text that’s visible to the user (this &lt;code&gt;Switch&lt;/code&gt; is unlabeled so this does not appear in this line)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CONTENT{See only Specials}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The content description provided by the widget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STATE{OFF}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If a widget is stateful, such as this &lt;code&gt;Switch&lt;/code&gt;, the current state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(action:FOCUS/A11Y_FOCUS/CLICK)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Actions available on the node, as defined by &lt;a href="https://dev.toAccessibilityNodeInfoCompat"&gt;&lt;code&gt;AccessibilityNodeInfoCompat&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:focusable:clickable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All other properties of the widget follow, delimited by &lt;code&gt;:&lt;/code&gt;. Possible values, in the order that they may appear are &lt;code&gt;focusable&lt;/code&gt;, &lt;code&gt;screenReaderfocusable&lt;/code&gt;, &lt;code&gt;focused&lt;/code&gt;, &lt;code&gt;selected&lt;/code&gt;, &lt;code&gt;scrollable&lt;/code&gt;, &lt;code&gt;clickable&lt;/code&gt;, &lt;code&gt;longClickable&lt;/code&gt;, &lt;code&gt;accessibilityFocused&lt;/code&gt;, &lt;code&gt;supportsTextLocation&lt;/code&gt;, &lt;code&gt;disabled&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;“Collection” information&lt;/td&gt;
&lt;td&gt;If things are in a &lt;code&gt;RecyclerView&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The screen &lt;em&gt;is&lt;/em&gt; actually a &lt;code&gt;RecyclerView&lt;/code&gt;, so let’s also take a look at what information we receive:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;At the end of:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;RecyclerView&lt;/code&gt; node itself, we see &lt;code&gt;:collection:R10C2&lt;/code&gt;. This indicates that this is a collection of views consisting of 10 rows, with two columns in each row. Talkback will announce the collection information the first time an item in the collection is selected. For example, if we tap on the Caramello Koala tile, Talkback will announce all the product information (based on the content description of the &lt;code&gt;ViewGroup&lt;/code&gt;) plus the location of the tile and the collection information (“Row 1, Column 2, In grid, 10 rows, 2 columns”).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;The item on the top right, we see &lt;code&gt;:item#r0c0&lt;/code&gt;. This is the row- and column-index (starting at 0) of the item relative to the list.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;The item on the top left, we see &lt;code&gt;:item#r0c1&lt;/code&gt;. Talkback will announce this item’s location as “Column 2”.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9, 10&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;Button&lt;/code&gt; is marked as &lt;code&gt;:invisible&lt;/code&gt; because it’s there, but not visible on the screen.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I was able to glean all this information from the &lt;a href="https://github.com/google/talkback/blob/f5d564fdc915a74d8cde4868608f307de9ccf957/utils/src/main/java/com/google/android/accessibility/utils/TreeDebug.java"&gt;LogTree file&lt;/a&gt; in &lt;a href="https://github.com/google/talkback"&gt;Talkback’s repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note that aside from logging the node tree, Talkback also logs the traversal order which may be useful when trying to figure out the order in which elements on the screen gets focus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing our issue… maybe 👀
&lt;/h3&gt;

&lt;p&gt;Going back to our original issue, the node tree gives us a clue (the cart menu item is in most screens of my app):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(1094239)652.ViewGroup:(948, 77 - 1080, 209):CONTENT{Cart: 2 items in Cart}(action:FOCUS/A11Y_FOCUS/CLICK):focusable:clickable
  (1095200)652.TextView:(1008, 107 - 1023, 140):TEXT{2}(action:A11Y_FOCUS):supportsTextLocation

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

&lt;/div&gt;



&lt;p&gt;AHA! It looks like both the &lt;code&gt;ViewGroup&lt;/code&gt; as a whole and the &lt;code&gt;TextView&lt;/code&gt; itself can have focus (it looks like the &lt;code&gt;TextView&lt;/code&gt; itself does not have a value for &lt;code&gt;CONTENT&lt;/code&gt;, which is what Talkback announces though), which &lt;em&gt;may&lt;/em&gt; explain why I sometimes hear just the number?&lt;/p&gt;

&lt;p&gt;I guess I still don’t have a definitive answer, but setting &lt;code&gt;android:importantForAccessibility="no"&lt;/code&gt; on the &lt;code&gt;TextView&lt;/code&gt; should not present a problem since enough context is already given to the user when the &lt;code&gt;ViewGroup&lt;/code&gt; gets focus.&lt;/p&gt;




&lt;p&gt;I hope that as more and more people become a11y allies that we also get more attention on a11y tooling, and more technical articles focused on supporting a11y beyond the basics. For instance, did you know that we can change what Talkback says to provide more context on actionable content? For example, when selecting this &lt;code&gt;ViewGroup&lt;/code&gt;:&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/AjZ6aIk"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DQhXk7bt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/AjZ6aIk.png" title="Partial screenshot with highlight"&gt;&lt;/a&gt;&lt;small&gt;Double-tapping will bring the user to the edit store or delivery address screen&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;Talkback will announce “Double-tap to change” instead of the default “Double-tap to activate”. The former gives the user more context about what is expected to happen when they interact with the element. Come to think of it, that’s a good idea for our next a11y post! See you then! 👌&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>android</category>
    </item>
    <item>
      <title>Enforcing Team Rules with Lint: Tests 🧐</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Fri, 20 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/enforcing-team-rules-with-lint-tests-ei1</link>
      <guid>https://dev.to/zmdominguez/enforcing-team-rules-with-lint-tests-ei1</guid>
      <description>&lt;p&gt;A few months ago, my team came upon an agreement that when leaving a TODO anywhere in our code, we need to always provide several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the person who is expected to address the TODO&lt;/li&gt;
&lt;li&gt;date when the TODO was left&lt;/li&gt;
&lt;li&gt;a comment or explanation on what needs to be done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created a &lt;a href="https://dev.to/zmdominguez/todo-live-templates-5199-temp-slug-8462254"&gt;live template&lt;/a&gt; to support adherence to this rule, but why not go one step further and integrate the rule into our daily workflow?&lt;/p&gt;

&lt;p&gt;We have previously seen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/zmdominguez/enforcing-team-rules-with-lint-5fej-temp-slug-8783794"&gt;the foundations we have started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/zmdominguez/enforcing-team-rules-with-lint-detectors-6af-temp-slug-2689931"&gt;what detectors are and how to write them&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Having tests for our custom Lint rule is really important. We do not want Lint to flag errors, uhm, erroneously. It results in frustration and users might just turn off our rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serving up files 💁
&lt;/h3&gt;

&lt;p&gt;Lint checks run on files, so for each of our test cases we need to provide mock files.&lt;/p&gt;

&lt;p&gt;Lint provides a &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestFile.java"&gt;&lt;code&gt;TestFile&lt;/code&gt;&lt;/a&gt; API that allows us to create these mock files inline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TestFiles.java(
    """
        package test.pkg;
        public class TestClass1 {
            // In a comment, mentioning "lint" has no effect
         }
    """
)


TestFiles.kotlin(
     """
        package test.pkg
        class TestClass {
            // In a comment, mentioning "lint" has no effect
        }
    """

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

&lt;/div&gt;



&lt;p&gt;I am using &lt;a href="https://kotlinlang.org/docs/reference/basic-types.html#string-literals"&gt;raw strings&lt;/a&gt; so that I do not have to worry about escaping special characters.&lt;/p&gt;

&lt;p&gt;It also gives us very nice syntax highlighting!&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/zkA28LD"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HaBgb8a4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/zkA28LD.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;Furthermore, if you choose “Edit Kotlin Fragment” from the hint, Android Studio will open up a file editor. Any changes you make in this editor will immediately reflect in your &lt;code&gt;TestFile&lt;/code&gt;. Pretty cool!&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/PQmaNBP"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5ozObLB2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/PQmaNBP.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;
&lt;h3&gt;
  
  
  Let’s get testing 🔬
&lt;/h3&gt;

&lt;p&gt;The gateway to our test cases is &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestLintTask.java"&gt;TestLintTask&lt;/a&gt;. We need to provide it the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;files we want to run our checks on&lt;/li&gt;
&lt;li&gt;the issue we are testing against&lt;/li&gt;
&lt;li&gt;expected result
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
fun testKotlinFileNormalComment() {
    TestLintTask.lint()
        .files(
            TestFiles.kotlin(
                """
                    package test.pkg

                    class TestClass {
                        // In a comment, mentioning "lint" has no effect
                    }
                """
            )
        )
        .issues(TodoDetector.ISSUE)
        .run()
        .expect("No warnings.")
}

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

&lt;/div&gt;


&lt;p&gt;Note that we have the option here of using &lt;code&gt;expect("No warnings.")&lt;/code&gt; or &lt;code&gt;expectClean()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the test cases where we expect an error to occur, we need to put in the text that Lint spits out (i.e. similar to what you see in the console when running &lt;code&gt;.gradlew :app:lintDebug&lt;/code&gt;). The trickiest thing about this is that the string has to match &lt;em&gt;exactly&lt;/em&gt;, including where the squiggly lines are.&lt;/p&gt;

&lt;p&gt;The easiest way to do this is to pass an empty string to &lt;code&gt;expect()&lt;/code&gt; and let the test fail. You can then copy-paste the error message into your test.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/TMaZBuX"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uWoCGDOs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/TMaZBuX.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;small&gt;Retrieving the message for an error scenario&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;I wrote a few tests for the detector covering Java and Kotlin files, incorrect date formats, and “TODO” casing. You can find them all &lt;a href="https://github.com/zmdominguez/sdk_sandbox/blob/main/checks/src/test/java/dev/zarah/lint/checks/TodoDetectorTest.kt"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Bringing it all together 🤝
&lt;/h3&gt;

&lt;p&gt;Now that we have written our tests, it’s finally time to integrate our Lint rule into our app!&lt;/p&gt;

&lt;p&gt;First we need to create our &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.kt"&gt;&lt;code&gt;IssueRegistry&lt;/code&gt;&lt;/a&gt; to let Lint know about our custom rule. We also need to provide an API value; for custom rules we can use the constant defined by the Lint API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Suppress("UnstableApiUsage")
class IssueRegistry : IssueRegistry() {
    override val issues: List&amp;lt;Issue&amp;gt; = listOf(
        TodoDetector.ISSUE
    )

    override val api = CURRENT_API
}

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

&lt;/div&gt;



&lt;p&gt;We then register our &lt;code&gt;IssueRegistry&lt;/code&gt; by creating a file with the fully qualified name of our file under a &lt;code&gt;META-INF/services/&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/main/resources/META-INF/services/dev.zarah.lint.checks.IssueRegistry

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

&lt;/div&gt;



&lt;p&gt;🙋 Most posts mention that adding this registry is enough, and I am probably doing something wrong but I found out that for my project, I still have to include my &lt;code&gt;IssueRegistry&lt;/code&gt; in the Manifest by adding this to my &lt;code&gt;build.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tasks {
  jar {
    manifest {
      attributes(
          "Lint-Registry-v2" to "dev.zarah.lint.checks.IssueRegistry"
      )
    }
  }
}

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

&lt;/div&gt;



&lt;p&gt;We have set up everything and now it’s time to consume our custom lint rule! In the modules where you want the rules applied, add a &lt;code&gt;lintChecks&lt;/code&gt; entry in the &lt;code&gt;dependencies&lt;/code&gt; closure, build your project, and everything should be good to go! 🏃‍♀️&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    lintChecks project(':checks')
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Seeing it in action 📽️
&lt;/h3&gt;

&lt;p&gt;Finally! We have come to the end of our journey! Here’s our detector in action:&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/oUivO3A"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---JkTUQuZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i.imgur.com/oUivO3A.gif" title="source: imgur.com"&gt;&lt;/a&gt;&lt;small&gt;Custom lint rule with quickfix&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;The source code for this Lint series is &lt;a href="https://github.com/zmdominguez/sdk_sandbox/tree/main/checks"&gt;available on Github&lt;/a&gt; (&lt;a href="https://github.com/zmdominguez/sdk_sandbox/pull/25"&gt;diff&lt;/a&gt;).&lt;/p&gt;




&lt;p&gt;🙇‍♀️ I have learned &lt;em&gt;so many&lt;/em&gt; things whilst I was doing this task.&lt;/p&gt;

&lt;p&gt;As I &lt;a href="https://twitter.com/zarahjutz/status/1329398431924699137"&gt;mentioned on Twitter&lt;/a&gt;, there’s barely any public documentation at all. The talks I’ve seen are way too advanced for me (for example, I &lt;em&gt;needed&lt;/em&gt; to understand what PSI and UAST were before it clicked and most talks barely even define them).&lt;/p&gt;

&lt;p&gt;There was a lot of trial and error, and so. much. guesswork. It was incredibly frustrating. Major props to &lt;a href="https://twitter.com/m_evans10"&gt;Mike Evans&lt;/a&gt; who patiently guided me through the hours and hours of pair programming that we did. If he didn’t help, I wouldn’t even know where to start. Sure I can copy-paste from samples but I want to understand &lt;em&gt;why&lt;/em&gt; things are done the way they are – that’s how I learn.&lt;/p&gt;

&lt;p&gt;I probably have made wrong assumptions whilst writing these posts 🤷‍♀️ but again, no documentation so this is the best I could do. 😅&lt;/p&gt;

&lt;p&gt;Anyway, what I want to say is, I usually only see the final output of other people’s work and sometimes I cannot help but feel jealous – how come they find it super easy whilst I’m out here crying because I don’t understand anything? I needed to remind myself that it’s okay to be frustrated, it’s okay to take your time to learn, and it’s okay to ask for help.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading (and/or watching) 📖
&lt;/h3&gt;

&lt;p&gt;If you’re keen to learn more, here are some resources about Lint and custom Lint rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/jCmJWOkjbM0"&gt;Coding in Style: Static Analysis with Custom Lint Rules, Android Dev Summit 2019&lt;/a&gt; (Alan Viverette, Rahul Ravikumar)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/p8yX5-lPS6o"&gt;Kotlin Static Analysis with Android Lint&lt;/a&gt; (Tor Norbye)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/"&gt;Lint API source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:docs/LINT.md"&gt;AndroidX documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/alexjlockwood/android-lint-checks-demo"&gt;Android Lint Checks demo&lt;/a&gt; (Alex Lockwood)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://groups.google.com/g/lint-dev"&gt;lint-dev Google group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>androidstudio</category>
      <category>lint</category>
    </item>
    <item>
      <title>Enforcing Team Rules with Lint: Detectors 🕵️</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Thu, 19 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/enforcing-team-rules-with-lint-detectors-1hdh</link>
      <guid>https://dev.to/zmdominguez/enforcing-team-rules-with-lint-detectors-1hdh</guid>
      <description>&lt;p&gt;A few months ago, my team came upon an agreement that when leaving a TODO anywhere in our code, we need to always provide several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the person who is expected to address the TODO&lt;/li&gt;
&lt;li&gt;date when the TODO was left&lt;/li&gt;
&lt;li&gt;a comment or explanation on what needs to be done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created a &lt;a href="https://dev.to/zmdominguez/todo-live-templates-5199-temp-slug-8462254"&gt;live template&lt;/a&gt; to support adherence to this rule, but why not go one step further and integrate the rule into our daily workflow?&lt;/p&gt;

&lt;p&gt;In this post, we build upon the &lt;a href="https://dev.to/zmdominguez/enforcing-team-rules-with-lint-5fej-temp-slug-8783794"&gt;foundations we have started&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Now that we have our module set up, we can start writing our detector.&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://dev.to/zmdominguez/enforcing-team-rules-with-lint-5fej-temp-slug-8783794"&gt;mentioned previously&lt;/a&gt;, detectors do the heavy lifting for our custom rule. To do so, it has to fulfil several roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Look for the relevant locations&lt;/li&gt;
&lt;li&gt;Find issues, if any, in those locations&lt;/li&gt;
&lt;li&gt;Report back any found issues to the user&lt;/li&gt;
&lt;li&gt;Suggest fixes for issues, if possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will look at each of these roles in turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coming to terms with Lint 🏛️
&lt;/h3&gt;

&lt;p&gt;Before we dive into detectors, it is useful to understand a bit of Lint lingo. This took me a really long time to understand and it was really frustrating. To say that there is no documentation at all is not a lie, there is no one place I can link to. A lot of the information here I sort of cobbled together from all the talks, posts, and hours and hours of research on Lint.&lt;/p&gt;

&lt;p&gt;When dealing with detectors, most things refer to something called UAST or PSI. For illustration purposes, let’s take this sample file from &lt;a href="https://gist.github.com/florina-muntenescu/08d751d843d55b75061039fee4e97931#file-customtypefacespan-kt"&gt;Florina Muntenescu’s gist&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.zdominguez.sdksandbox

/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import android.graphics.Typeface
import android.text.TextPaint
import android.text.style.MetricAffectingSpan

/**
 * Span that changes the typeface of the text used to the one provided. The style set before will
 * be kept.
 */
open class CustomTypefaceSpan(private val font: Typeface?) : MetricAffectingSpan() {

    override fun updateMeasureState(textPaint: TextPaint) = update(textPaint)

    override fun updateDrawState(textPaint: TextPaint) = update(textPaint)

    private fun update(textPaint: TextPaint) {
        textPaint.apply {
            val old = typeface
            val oldStyle = old?.style ?: 0

            // keep the style set before
            val font = Typeface.create(font, oldStyle)
            typeface = font
        }
    }
}

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  PSI
&lt;/h4&gt;

&lt;p&gt;PSI, or &lt;strong&gt;P&lt;/strong&gt; rogram &lt;strong&gt;S&lt;/strong&gt; tructure &lt;strong&gt;I&lt;/strong&gt; nterface, is &lt;a href="https://jetbrains.org/intellij/sdk/docs/basics/architectural_overview/psi.html"&gt;traditionally used by IntelliJ&lt;/a&gt; to model source files via &lt;em&gt;elements&lt;/em&gt;. The &lt;a href="https://plugins.jetbrains.com/plugin/227-psiviewer"&gt;PsiViewer plugin&lt;/a&gt; is really useful in helping you visualise what this means.&lt;/p&gt;

&lt;p&gt;I like to think of PSI as the blueprint of the file. It shows each and every single element, including whitespaces and braces (it can even tell you if it’s a left brace or a right brace!)&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/eaURHsS"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KnF4uFKu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/eaURHsS.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;small&gt;PSI for the &lt;code&gt;CustomTypefaceSpan&lt;/code&gt; Kotlin file&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;
&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/H2mSPhk"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DImkGvM7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/H2mSPhk.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;small&gt;PSI for an XML file&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;
&lt;h4&gt;
  
  
  UAST
&lt;/h4&gt;

&lt;p&gt;UAST, or &lt;strong&gt;U&lt;/strong&gt; niversal &lt;strong&gt;A&lt;/strong&gt; bstract &lt;strong&gt;S&lt;/strong&gt; yntax &lt;strong&gt;T&lt;/strong&gt; ree, was created by Jetbrains to describe Java and Kotlin syntax trees. A &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree"&gt;syntax tree&lt;/a&gt; shows the hierarchical structure of our code, illustrating all the rules and constructs that the code will follow via &lt;em&gt;nodes&lt;/em&gt;.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/VJHlA7N"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LB2JKtzc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/VJHlA7N.jpg" title="source: imgur.com"&gt;&lt;/a&gt;&lt;small&gt;Partial UAST for the &lt;code&gt;CustomTypefaceSpan&lt;/code&gt; Kotlin file&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;When dealing with UAST, we do not really care if we are looking at a Java file or a Kotlin file. Instead we see the &lt;em&gt;logical&lt;/em&gt; branches in our code.&lt;/p&gt;
&lt;h4&gt;
  
  
  PSI vs UAST
&lt;/h4&gt;

&lt;p&gt;It took a while for me to wrap my head around these concepts, and I finally sort of understood it with with an IKEA analogy. UAST is like the photos you see on the catalogue. It describes what the furniture is – a chest of drawers with white knobs, a shelf, it has a specific kind of door. PSI, on the other hand, is like the assembly instructions for that furniture. Get this plank, put a screw in here, a bolt there.&lt;/p&gt;
&lt;h3&gt;
  
  
  Just get on with it, Zarah
&lt;/h3&gt;

&lt;p&gt;Let’s go ahead and make our detector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import com.android.tools.lint.detector.api.Detector

@Suppress("UnstableApiUsage")
class TodoDetector : Detector() {
}

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Detector&lt;/code&gt; is the base class of all detectors. It is marked as &lt;code&gt;@Beta&lt;/code&gt;, so I added the &lt;code&gt;@Suppress&lt;/code&gt; annotation there to confirm that yes, I know this might break.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Detector&lt;/code&gt; is pretty generic (except for &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java"&gt;&lt;code&gt;ResourceXmlDetector&lt;/code&gt;&lt;/a&gt;), and we can get much more out of Lint if we can indicate what specific detector we need. There are a few specialised interfaces we can use depending on what type of thing we need to run our rule on:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/BinaryResourceScanner.kt"&gt;BinaryResourceScanner&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For scanning binaries like images, and resources inside &lt;code&gt;res/raw&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="//lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ClassScanner.kt"&gt;ClassScanner&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For &lt;code&gt;.class&lt;/code&gt; files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="//lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/GradleScanner.kt"&gt;GradleScanner&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For &lt;code&gt;.gradle&lt;/code&gt; files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="//lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/OtherFileScanner.kt"&gt;OtherFileScanner&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Uhm, others&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="//lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceFolderScanner.kt"&gt;ResourceFolderScanner&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For looking at resource folders (not the contents, just the folder!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="//lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/SourceCodeScanner.kt"&gt;SourceCodeScanner&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For scanning source code like Java or Kotlin files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="//lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlScanner.kt"&gt;XmlScanner&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For XML files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the purposes of our TODO detector, we will need to implement the &lt;code&gt;SourceCodeScanner&lt;/code&gt; interface since we want to look at Java and Kotlin files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Suppress("UnstableApiUsage")
class TodoDetector : Detector(), SourceCodeScanner {

}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Being specific
&lt;/h3&gt;

&lt;p&gt;There is another term that most talks on Lint mention a lot, and that is “visit”. I haven’t seen an exact accurate definition of the term, but from what I gather this is what we call the act of Lint getting to a particular UAST node or PSI element.&lt;/p&gt;

&lt;p&gt;This means if we want to look at usages of a method for example, we need to “visit” methods and figure out if the issue exists there. If we want to write a rule that checks constraints in a layout file, then we probably need to “visit” XML attributes and values.&lt;/p&gt;

&lt;p&gt;There is, indeed, a proper way to be specific in Lint about what locations we want to be visited. The &lt;code&gt;Detector&lt;/code&gt; povides several methods for this. Note that &lt;strong&gt;MOST&lt;/strong&gt; of them start with &lt;code&gt;get&lt;/code&gt; but not all of them do. 😅&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/qxM3fQO"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uWJ7v7MX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/qxM3fQO.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;small&gt;Available getApplicable* methods&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;All of these methods are available to you regardless of what kind of &lt;code&gt;*Scanner&lt;/code&gt; interface our detector implements as they are all in the base &lt;code&gt;Detector()&lt;/code&gt; class. We need to make sure to implement at least one of these methods to signal to Lint that we care about those locations.&lt;/p&gt;

&lt;p&gt;Since we want to look at comments, the best fit for our usecase is &lt;code&gt;getApplicableUastTypes()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next we need to specify what specific kind of UAST node we are looking for. There &lt;em&gt;is&lt;/em&gt; a &lt;a href="https://upsource.jetbrains.com/idea-ce/file/idea-ce-5af2b2de76fa6e57d1b6c4c6fab9c00e148d3b5c/uast/uast-common/src/org/jetbrains/uast/baseElements/UComment.kt?nav=715:723:focused&amp;amp;line=20&amp;amp;preview=false"&gt;&lt;code&gt;UComment&lt;/code&gt;&lt;/a&gt; UAST type which sounds like exactly the type we need.&lt;/p&gt;

&lt;p&gt;There is one caveat though – if we look at the generated UAST for our sample file, &lt;code&gt;UComment&lt;/code&gt; does not make an appearance at all! However, we &lt;em&gt;do&lt;/em&gt; see via PsiViewer that a &lt;a href="https://upsource.jetbrains.com/idea-ce/file/idea-ce-4682003011bb42ffdb872d081e79d300bb393d17/platform/core-api/src/com/intellij/psi/PsiComment.java"&gt;&lt;code&gt;PsiComment&lt;/code&gt;&lt;/a&gt; is present.&lt;/p&gt;

&lt;p&gt;If we traverse the UAST backwards (upwards?), we eventually get to &lt;code&gt;UFile&lt;/code&gt; which encompasses all the possible places that a comment can reside in. Perfect, let’s go ahead and use that as the UAST type we care about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Suppress("UnstableApiUsage")
class TodoDetector : Detector(), SourceCodeScanner {
    override fun getApplicableUastTypes(): List&amp;lt;Class&amp;lt;out UElement&amp;gt;&amp;gt; {
        return listOf(UFile::class.java)
    }
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Receiving callbacks
&lt;/h3&gt;

&lt;p&gt;Now that we have told Lint what kind of location we care about, we need to tell it to let us know if it encounters that location.&lt;/p&gt;

&lt;p&gt;Again, there are numerous options available to us. Note that &lt;strong&gt;MOST&lt;/strong&gt; of them of them start with &lt;code&gt;visit&lt;/code&gt;, but not all of them do. 😅 😅&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/yeTkTag"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JXbKUV1K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/yeTkTag.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;small&gt;Available visit*** methods&lt;/small&gt;&lt;br&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;In fact, judging by the names, none of these match what we need. Since we have selected that we care about UAST types, what we need is actually called &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.kt;l=543;drc=7465b820deacf7e7acc4e93d5f73a73633b7bfcb"&gt;&lt;code&gt;createUastHandler()&lt;/code&gt;&lt;/a&gt;. Our next task is to create this handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override fun createUastHandler(context: JavaContext): UElementHandler {
    return TodoScanner(context)
}

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  A very important side note 🛑
&lt;/h4&gt;

&lt;p&gt;It is important to remember that each of the &lt;code&gt;getApplicable*&lt;/code&gt; methods map to a corresponding &lt;code&gt;visit*&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Let’s take this example from the &lt;a href="https://youtu.be/jCmJWOkjbM0?t=734"&gt;ADS 2019 talk on Lint&lt;/a&gt;. We want to create a rule that prevents users from calling &lt;code&gt;Log.wtf()&lt;/code&gt; anywhere in an app. In it, they are overriding &lt;code&gt;getApplicableMethodNames()&lt;/code&gt; so the corresponding callback method to override is &lt;code&gt;visitMethodCall()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Similarly, if we want to look at XML attributes we would override &lt;code&gt;getApplicableAttributes()&lt;/code&gt; and the corresponding &lt;code&gt;visitAttribute()&lt;/code&gt; callback. Alex Lockwood demonstrates this in &lt;a href="https://github.com/alexjlockwood/android-lint-checks-demo/blob/master/checks/src/main/java/com/lyft/android/lint/checks/DeprecatedPurpleColorXmlDetector.kt"&gt;this sample project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is &lt;em&gt;extremely&lt;/em&gt; important to double check that you are using the correct combination because Lint will not tell you if are doing it incorrectly (i.e. everything will still compile). I haven’t found any documentation on the mapping of &lt;code&gt;getApplicable*&lt;/code&gt; against &lt;code&gt;visit*&lt;/code&gt; but this what I gather from my experiments:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;get*&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;visit*&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Framework examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;applicableAnnotations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visitAnnotationUsage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lint-checks/src/main/java/androidx/build/lint/BanKeepAnnotation.kt"&gt;BanKeepAnnotation&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;applicableSuperClasses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visitClass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java"&gt;OnClickDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicableAsmNodeTypes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;checkInstruction&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/FieldGetterDetector.java"&gt;FieldGetterDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicableAttributes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visitAttribute&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java"&gt;DuplicateIdDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicableCallNames&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;checkCall&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android/platform/superproject/+/master:tools/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java"&gt;LocaleDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicableCallOwners&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getApplicableCallNames&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java"&gt;SecureRandomGeneratorDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicableConstructorTypes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visitConstructor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android/platform/superproject/+/master:tools/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DateFormatDetector.java"&gt;DateFormatDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicableElements&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visitElement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/InstantAppDetector.java"&gt;InstantAppDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicableMethodNames&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visitMethodCall&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ReadParcelableDetector.java"&gt;ReadParcelableDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicablePsiTypes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;createPsiVisitor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicableReferenceNames&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;visitReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java"&gt;AlwaysShowActionDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getApplicableUastTypes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;createUastHandler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java"&gt;OverdrawDetector&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Implementing the scanner
&lt;/h3&gt;

&lt;p&gt;I felt it worth the effor of understanding the relationship between &lt;code&gt;get*&lt;/code&gt; and &lt;code&gt;visit*&lt;/code&gt; methods because that same principle applies when implementing our handler.&lt;/p&gt;

&lt;p&gt;When we defined our detector, we told Lint that we want to watch for UAST type of &lt;code&gt;UFile&lt;/code&gt; and thus we must implement the appropriate callback in our scanner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TodoScanner(private val context: JavaContext) : UElementHandler() {

    override fun visitFile(node: UFile) {

    }
}

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

&lt;/div&gt;



&lt;p&gt;For each UAST type we provide in &lt;code&gt;getApplicableUastTypes()&lt;/code&gt;, we should also have the corresponding the &lt;code&gt;visit*&lt;/code&gt; implementations. I will not enumerate them here because ✨ &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/UElementHandler.kt"&gt;there are a LOT&lt;/a&gt; ✨ but this time Lint actually helps you out!&lt;/p&gt;

&lt;p&gt;For example, if we say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override fun getApplicableUastTypes(): List&amp;lt;Class&amp;lt;out UElement&amp;gt;&amp;gt; {
    return listOf(UMethod::class.java, UClass::class.java, UFile::class.java)
}

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

&lt;/div&gt;



&lt;p&gt;We would need to implement &lt;code&gt;visitMethod&lt;/code&gt;, &lt;code&gt;visitClass&lt;/code&gt;, and &lt;code&gt;visitFile&lt;/code&gt; in our scanner. Lint will tell you at runtime if it encounters a particular UAST type but cannot find the appropriate callback.&lt;/p&gt;

&lt;p&gt;If like me you have no idea what the possible U-values are or how a file’s UAST would look like, it is quite challenging to figure out what to do at this point. Unfortunately there is no UastViewer that I know of, and the only way to explore this realm is through good old-fashioned &lt;code&gt;println&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override fun visitFile(node: UFile) {
    val nodesString = node.asRecursiveLogString()
    println(nodesString)
}

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

&lt;/div&gt;



&lt;p&gt;This gives you a string with the full UAST structure of the file being analysed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding fault
&lt;/h3&gt;

&lt;p&gt;Now that we have a better understanding of what is going on, let’s go ahead and implement the soul of our scanner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override fun visitFile(node: UFile) {

    val allComments = node.allCommentsInFile
    allComments.forEach { comment -&amp;gt;
        val commentText = comment.text

        // Ignore regular comments that are not TODOs
        // If we find a TODO that does not follow the convention, show an error
        if (commentText.contains("TODO", ignoreCase = true) &amp;amp;&amp;amp; !isValidComment(
                commentText)) {
            reportUsage(context, comment)
        }
    }
}

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;isValid&lt;/code&gt; method checks if the comment follows our team rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private fun isValidComment(commentText: String): Boolean {
    val regex = Regex("//\\s+TODO-\\w*\\s+\\(\\d{8}\\):.*")
    return commentText.matches(regex)
}

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

&lt;/div&gt;



&lt;p&gt;And if it does not, report this as an issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Responsible reporting 📢
&lt;/h3&gt;

&lt;p&gt;When we find a comment that violates the contract, we want to give our users a clear definition of what went wrong and how to fix the issue.&lt;/p&gt;

&lt;p&gt;Remember the anatomy of an issue we talked about &lt;a href="https://dev.to/zmdominguez/enforcing-team-rules-with-lint-5fej-temp-slug-8783794"&gt;in the previous post&lt;/a&gt;? Let’s use that knowledge to define our issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val ISSUE: Issue = Issue.create(
    id = "UnassignedTodo",
    briefDescription = "TODO with no assignee",
    explanation =
    """
        This check makes sure that each TODO is assigned to somebody.
    """.trimIndent(),
    category = Category.CORRECTNESS,
    priority = 3,
    severity = Severity.ERROR,
    implementation = IMPLEMENTATION
)

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

&lt;/div&gt;



&lt;p&gt;The last parameter required by &lt;code&gt;Issue.create()&lt;/code&gt; is an &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Implementation.java"&gt;&lt;code&gt;Implementation&lt;/code&gt;&lt;/a&gt; that maps the issue being reported with the detector responsible for finding that issue.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;Implementation&lt;/code&gt; requires a &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.kt"&gt;&lt;code&gt;Scope&lt;/code&gt;&lt;/a&gt; – which tells Lint what kind of files our implementation is interested in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private val IMPLEMENTATION = Implementation(
    TodoDetector::class.java,
    Scope.JAVA_FILE_SCOPE
)

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

&lt;/div&gt;



&lt;p&gt;The naming is indeed misleading but &lt;code&gt;JAVA_FILE_SCOPE&lt;/code&gt; means both Java and Kotlin files will be considered for our rule when running Lint.&lt;/p&gt;

&lt;p&gt;This means at this point in our implementation it is absolutely critical that these information need to be compatible:&lt;br&gt;&lt;br&gt;
✅ the interface we are implementing for our detector&lt;br&gt;&lt;br&gt;
✅ the overridden &lt;code&gt;getApplicable*&lt;/code&gt; method&lt;br&gt;&lt;br&gt;
✅ the overridden &lt;code&gt;visit*&lt;/code&gt; method&lt;br&gt;&lt;br&gt;
✅ (since we using UAST type) the overridden &lt;code&gt;visit*&lt;/code&gt; methods in our &lt;code&gt;UElementHandler&lt;/code&gt;&lt;br&gt;&lt;br&gt;
✅ the &lt;code&gt;Scope&lt;/code&gt; of the issue’s implementation&lt;/p&gt;
&lt;h3&gt;
  
  
  A little help from my friends
&lt;/h3&gt;

&lt;p&gt;We can help our users get more value out of our custom Lint rule by helping them fix the issue. Lint allows us to provide a quickfix option and users can press SHIFT+ALT+ENTER (or ALT+ENTER then ENTER) and apply the changes we have proposed.&lt;/p&gt;

&lt;p&gt;For our detector, we want to format the comment properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// TODO This is an improperly formatted comment

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

&lt;/div&gt;



&lt;p&gt;A comment like the one above would be flagged by our detector as improperly formatted (remember we look for the &lt;code&gt;TODO&lt;/code&gt; string). To fix it, we would need to add the user’s name and today’s date. Luckily &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintFix.java"&gt;&lt;code&gt;LintFix&lt;/code&gt;&lt;/a&gt; allows us to do it pretty easily:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Look for the instance of the "TODO" literal
val oldPattern = Regex("TODO|todo")

// Our proposed fix concatenates the user's name
// and today's date in the correct format
val replacementText = "TODO-${System.getProperty("user.name")} " +
    "(${
        LocalDate.now()
            .format(DateTimeFormatter.ofPattern("yyyyMMdd"))
    }):"

val quickfixData = LintFix.create()
    .name("Assign this TODO")
    .replace()
    .pattern(oldPattern.pattern)
    .with(replacementText)
    .robot(true) // Can be applied automatically.
    .independent(true) // Does not conflict with other auto-fixes.
    .build()

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

&lt;/div&gt;



&lt;p&gt;Now that we have the issue, implementation, and quickfix created, the only thing remaining is to tell Lint where to indicate that an issue was encountered. It is important to provide an accurate location because this tells the IDE where to show the error:&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/ithhwN2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tyrmSI1j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ithhwN2.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;And where to put the red squiggly lines in the Lint report:&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/lHrTC8w"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8yU4Pbcy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/lHrTC8w.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;For this rule, it’s good enough for me to highlight the full comment. We can now finish up our &lt;code&gt;reportUsage&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private fun reportUsage(
    context: JavaContext,
    comment: UComment
) {
    context.report(
        issue = Companion.ISSUE,
        location = context.getLocation(comment),
        message = "Please make sure to assign the TODO, include today's date in YYYYMMDD format, and the comment is properly formatted.",
        quickfixData = quickfixData
    )
}

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

&lt;/div&gt;



&lt;p&gt;We have finally finished our detector! 💪 This has been an honest-to-goodness brain dump. Writing my first Lint rule has truly been a challenge and there are stretches of time where I was literally staring at the screen periodically yelling WHAT at no one in particular. Good thing we’ve all been working from home. 😅&lt;/p&gt;




&lt;p&gt;In the next post in this series, we get to write some tests and if everything goes well, we get to actually use our detector! Stay tuned!&lt;/p&gt;

</description>
      <category>androidstudio</category>
      <category>lint</category>
    </item>
    <item>
      <title>Enforcing Team Rules with Lint 👩‍🔧</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Wed, 18 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/enforcing-team-rules-with-lint-3a2a</link>
      <guid>https://dev.to/zmdominguez/enforcing-team-rules-with-lint-3a2a</guid>
      <description>&lt;p&gt;A few months ago, my team came upon an agreement that when leaving a TODO anywhere in our code, we need to always provide several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the person who is expected to address the TODO&lt;/li&gt;
&lt;li&gt;date when the TODO was left&lt;/li&gt;
&lt;li&gt;a comment or explanation on what needs to be done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But memories fade and sometimes people forget. So I made a live template to make it easier for everyone to adhere to the rule. A simple ALT+ENTER and tada, there’s the TODO template for you to fill in:&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/LjaiHU5"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--laxCSQkc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i.imgur.com/LjaiHU5.gif" title="source: imgur.com"&gt;&lt;/a&gt;&lt;small&gt;Custom template in action&lt;/small&gt;&lt;br&gt; &lt;br&gt;
&lt;/center&gt;

&lt;p&gt;If you’re curious to know more about how this was done, I &lt;a href="https://dev.to/zmdominguez/todo-live-templates-5199-temp-slug-8462254"&gt;wrote about implementing a live template here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Everything seemed fine and dandy, but after a few months I noticed that even though we have the template, people occasionally ✨ forget ✨ to fill it in.&lt;/p&gt;

&lt;p&gt;We can keep on reminding people to use the template, but I don’t want to be that annoying person saying things over and over. Wouldn’t it be better if we incorporate this team rule into the developer workflow? So it got me thinking, why not write a custom Lint rule to remove the human intervention and let the tool do its job?&lt;/p&gt;
&lt;h3&gt;
  
  
  A great idea
&lt;/h3&gt;

&lt;p&gt;Lint is a static analyser and the built-in rules are great! They help identify common problems (forgetting to &lt;a href="https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.kt"&gt;call &lt;code&gt;super()&lt;/code&gt;&lt;/a&gt;), possible bugs (forgetting to &lt;a href="https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ConstraintLayoutDetector.kt"&gt;constrain a view&lt;/a&gt;), or potential optimisations (detecting &lt;a href="https://android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java"&gt;overdraw&lt;/a&gt;) in our code and gives us the chance to correct them immediately.&lt;/p&gt;

&lt;p&gt;These rules are flexible. We can pick and choose which ones we want to &lt;a href="https://developer.android.com/studio/write/lint#config"&gt;exclude from running&lt;/a&gt;, we can choose to &lt;a href="https://developer.android.com/studio/write/lint#gradle"&gt;fail the build if something goes wrong&lt;/a&gt;, we can even &lt;a href="https://developer.android.com/studio/write/lint#snapshot"&gt;identify a specific issue to be ignored&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are a lot of great talks out there that give an overview of Lint’s history, philosophy, and features especially &lt;a href="https://youtu.be/p8yX5-lPS6o"&gt;this one by Tor Norbye from KotlinConf 2017&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lint has been around for a while, and support for writing our own Lint checks has &lt;em&gt;also&lt;/em&gt; been around for a while, so surely there are references out there right? There’s this good overview by John Rodriguez &lt;a href="https://www.droidcon.com/media-detail?video=329360652"&gt;from Droidcon NYC 2017&lt;/a&gt; and this very informational talk by Alan Viverette and Rahul Ravikumar &lt;a href="https://www.youtube.com/watch?v=jCmJWOkjbM0&amp;amp;vl=en"&gt;from Android Dev Summit 2019&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;They make it look easy enough. &lt;strong&gt;Surely&lt;/strong&gt; &lt;em&gt;I&lt;/em&gt; can do this. 💃&lt;/p&gt;
&lt;h3&gt;
  
  
  The worst idea
&lt;/h3&gt;

&lt;p&gt;Turns out that it &lt;em&gt;is&lt;/em&gt; easy enough… if you know what you’re doing. And ooooh mama I &lt;u&gt;DO NOT&lt;/u&gt; know what I was doing.&lt;/p&gt;

&lt;p&gt;The &lt;a href="http://tools.android.com/tips/lint-custom-rules"&gt;Android tools site&lt;/a&gt; looks abandoned 🙈. The sample rules are a &lt;a href="https://github.com/googlesamples/android-custom-lint-rules"&gt;bit basic&lt;/a&gt; but aside from helping me figure out how to set things up, it doesn’t &lt;em&gt;really&lt;/em&gt; tell me anything.&lt;/p&gt;

&lt;p&gt;But Zarah, you say, why not just copy off of the ones in the platform? I did, and there are &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/"&gt;HUNDREDS of them&lt;/a&gt;. It is overwhelming to figure out which one to look at first.&lt;/p&gt;

&lt;p&gt;I guarantee you that if someone asks me to do this again in about three months, I would have totally forgotten what I did or how I made it work (to be honest I still do not understand how it &lt;em&gt;actually&lt;/em&gt; works 🤷‍♀️). Fair warning: I will constantly be calling out things that I do not understand or was just guessing at.&lt;/p&gt;

&lt;p&gt;Let’s get to it. 🙇‍♀️&lt;/p&gt;
&lt;h3&gt;
  
  
  Getting cosy with it
&lt;/h3&gt;

&lt;p&gt;There are some key concepts we need to understand before diving into the code. Lint is fueled by &lt;strong&gt;issues&lt;/strong&gt; and &lt;strong&gt;detectors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issues&lt;/strong&gt; identify implementations that we want to highlight as incorrect or potential sources of bugs. Take this excerpt from a report and let us look at the anatomy of an issue from this perspective:&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/1aQUihs"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BsNMiRC5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/1aQUihs.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt; &lt;br&gt;
&lt;/center&gt;

&lt;p&gt;An issue has:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ref&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Brief description&lt;/td&gt;
&lt;td&gt;A summary of what went wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Explanation&lt;/td&gt;
&lt;td&gt;Provides details of why this is considered an issue. You can also suggest possible fixes, or provide links to relevant documentation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;ID&lt;/td&gt;
&lt;td&gt;A unique identifier for the issue. This is what developers use to suppress reporting of this issue, i.e. what goes into &lt;code&gt;@Supress&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Category&lt;/td&gt;
&lt;td&gt;Identifies an area where this issue can be bucketed. There is a wide variety of &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Category.kt;l=90;drc=e48839385a9fd74f17265029dbdac3ef05c0cec6"&gt;available categories&lt;/a&gt;, covering areas from &lt;code&gt;A11Y&lt;/code&gt; (accessibility) to &lt;code&gt;INTEROPERABILITY_JAVA&lt;/code&gt; (issues when calling Kotlin from Java) to &lt;code&gt;TYPOGRAPHY&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Severity&lt;/td&gt;
&lt;td&gt;This influences how this issue is treated and can be one of &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Severity.kt"&gt;several options&lt;/a&gt;, with &lt;code&gt;FATAL&lt;/code&gt; being the most severe (the build is aborted and nopes out). Users can force the build to fail if an &lt;code&gt;ERROR&lt;/code&gt; is encountered with &lt;code&gt;lintOptions.isAbortOnError = true&lt;/code&gt; in their &lt;code&gt;build.gradle&lt;/code&gt; file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Priority&lt;/td&gt;
&lt;td&gt;A number from 1 to 10, with 10 indicating the highest priority&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Detectors&lt;/strong&gt; do the heavy lifting and figure out where the issues are. They can look in virtually any sort of file – Manifest? ✅, resource file? ✅ Gradle files? ✅&lt;/p&gt;

&lt;p&gt;A single detector can identify any number of related issues, and can report each of those issues individually.&lt;/p&gt;

&lt;p&gt;Note that Lint calls all existing detectors in a &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Detector.kt;l=52;drc=7465b820deacf7e7acc4e93d5f73a73633b7bfcb"&gt;pre-defined order&lt;/a&gt;. This means that if a Lint rule needs to check something in both Kotlin and XML files (like resource usages, for example) the order in which checks are executed matters.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up
&lt;/h3&gt;

&lt;p&gt;Our custom rules need a home; and their home is a new module. Right click on your project and select &lt;code&gt;New &amp;gt; Module&lt;/code&gt; then choose &lt;code&gt;Java or Kotlin Library&lt;/code&gt;&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/ukHNc2W"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ain0jvbT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ukHNc2W.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;In the freshly-created &lt;code&gt;build.gradle&lt;/code&gt; file, add the dependencies for Lint. (Note: I use the &lt;a href="https://docs.gradle.org/nightly/userguide/kotlin_dsl.html"&gt;Kotlin DSL&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// The Lint version is strongly tied to the Android Gradle Plugin (AGP) version
// Add 23 to the major version number of AGP (x in x.y.z)
// The project is currently dependent on AGP v4.1.1
val lintVersion = "27.1.1"

// Lint
compileOnly("com.android.tools.lint:lint-api:${lintVersion}")
compileOnly("com.android.tools.lint:lint-checks:${lintVersion}")

// Lint testing
testImplementation("com.android.tools.lint:lint:${lintVersion}")
testImplementation("com.android.tools.lint:lint-tests:${lintVersion}")
testImplementation("junit:junit:4.13.1")

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

&lt;/div&gt;






&lt;p&gt;The next part of our Lint journey is going to be long and treacherous. So let’s take a quick break here, make sure to watch the talks linked above and ⏸️ stay tuned for the next post in this series where we finally get to write our very own detector!&lt;/p&gt;

</description>
      <category>androidstudio</category>
      <category>lint</category>
    </item>
    <item>
      <title>On-Device Debugging Part V: Strut Your Stuff</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Sat, 20 Jul 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/on-device-debugging-part-v-strut-your-stuff-15g9</link>
      <guid>https://dev.to/zmdominguez/on-device-debugging-part-v-strut-your-stuff-15g9</guid>
      <description>&lt;p&gt;Over the past year, my team have been steadily building a Developer Options screen for our app. It is a simple &lt;a href="https://developer.android.com/reference/androidx/preference/PreferenceScreen.html"&gt;&lt;code&gt;PreferenceScreen&lt;/code&gt;&lt;/a&gt; available on debug builds that help us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;figure out what’s going on without needing to be attached to a computer&lt;/li&gt;
&lt;li&gt;test various configurations without re-installing&lt;/li&gt;
&lt;li&gt;have a host for various experimentations we are trying to explore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this series of posts, I will share what these various options are and how we made them.&lt;/p&gt;

&lt;p&gt;Read the other posts in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zarah.dev/2019/06/22/debug-options-toggles.html"&gt;Part I: Now It’s On, Now It’s Off&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zarah.dev/2019/06/24/debug-options-timber.html"&gt;Part II: Timbeeeeeeer!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zarah.dev/2019/07/01/debug-options-info.html"&gt;Part III: Inspect, Reset, Repeat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zarah.dev/2019/07/08/debug-options-actions.html"&gt;Part IV: Log All The Things!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(If you are not familiar with &lt;a href="https://developer.android.com/reference/kotlin/androidx/preference/PreferenceFragmentCompat.html"&gt;&lt;code&gt;PreferenceFragmentCompat&lt;/code&gt;&lt;/a&gt;, I highly suggest to read about that first before proceeding. You can start with this &lt;a href="https://developer.android.com/guide/topics/ui/settings.html"&gt;AndroidX guide&lt;/a&gt; on Settings.)&lt;/p&gt;




&lt;p&gt;Working on an app means we are constantly tweaking how things look. Change this shade of green, use this widget instead of that, move this thing to there.&lt;/p&gt;

&lt;p&gt;In the last part of our on-device debugging series, we look at some of the ways we showcase how things should look in our app.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/mOr5Aou"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6sPoixhs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/mOr5Aou.png%3F1" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;
    &lt;br&gt;&lt;br&gt;
&lt;small&gt;Design Playground and Shortcuts&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;
&lt;h3&gt;
  
  
  Design Playground
&lt;/h3&gt;

&lt;p&gt;A few weeks ago, we went all in and switched our whole app to use the new &lt;a href="https://material.io/design/material-theming/implementing-your-theme.html"&gt;Material Design Components theme&lt;/a&gt;. (Nick Rout has an &lt;a href="https://medium.com/over-engineering/setting-up-a-material-components-theme-for-android-fbf7774da739"&gt;awesome article&lt;/a&gt; showing you more details on how to do this for your app.)&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NdtUJDG6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/D8HdziIUIAE3E2y.jpg" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--QAVeKriA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/790815005901922304/mBP_2hRs_normal.jpg" alt="Zarah Dominguez 🦉 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Zarah Dominguez 🦉
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @zarahjutz
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P4t6ys1m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      My team is SMASHING IT. 💪 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      06:26 AM - 03 Jun 2019
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1135432513688465408" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1135432513688465408" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      0
      &lt;a href="https://twitter.com/intent/like?tweet_id=1135432513688465408" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      16
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;We fully expected that some widgets might look strange and we might have to tweak some things a bit. Taking inspiration from the &lt;a href="https://github.com/material-components/material-components-android/blob/master/docs/catalog-app.md"&gt;MDC Catalog app&lt;/a&gt;, we came up with a Theme Showcase page.&lt;/p&gt;

&lt;p&gt;This page has all the widgets as well as the custom components we use throughout the app.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/jfqSBoB"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I_5XjmpN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/jfqSBoB.png%3F1" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;
    &lt;a href="https://imgur.com/zbzyEd0"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--knLQHDbL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/zbzyEd0.png%3F1" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;small&gt;Widget showcase&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;This has been really useful in helping us figure out how things look as we go through adapting MDC. This page has also been handy when we are tweaking something with our design team, and as a reference page for when we are visually testing a new feature.&lt;/p&gt;

&lt;p&gt;Since we also have multiple themes in the app, we included a theme switcher that allows us to see how everything looks in each of the themes.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
&lt;a href="https://imgur.com/pC3qSNY"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ns18h1Un--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/pC3qSNY.png%3F1" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;small&gt;Dynamically switch themes&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;This switcher is accessed through the overflow menu. When the user chooses a theme, we save the value to a &lt;code&gt;SharedPreferences&lt;/code&gt; file.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;For simplicity, we only save the position of the chosen theme:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We then need to make sure that we get back the chosen theme:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And set it on our Activity’s &lt;code&gt;onCreate&lt;/code&gt; via &lt;code&gt;setTheme()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shortcuts
&lt;/h3&gt;

&lt;p&gt;Some parts of the app are a bit cumbersome to get to.&lt;/p&gt;

&lt;p&gt;When we were building our app’s order confirmation page, it was getting annoying having to complete an order each time to see how it looks whenever we change something in the layout.&lt;/p&gt;

&lt;p&gt;As we saw in my previous post about &lt;a href="https://dev.to/zmdominguez/shortcuts-to-shortcuts-572d"&gt;app shortcuts&lt;/a&gt;, we can put in a &lt;code&gt;targetPackage&lt;/code&gt; and a &lt;code&gt;targetClass&lt;/code&gt; in our &lt;code&gt;Intent&lt;/code&gt;s:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Having these shortcuts available helped us skip having to navigate through multiple steps to get to a particular screen.&lt;/p&gt;




&lt;p&gt;And with that, we wrap up this series on on-device debugging options. We have seen how we can &lt;a href="https://zarah.dev/2019/06/22/debug-options-toggles.html"&gt;make feature toggles more user-friendly&lt;/a&gt;, how we can &lt;a href="https://zarah.dev/2019/06/24/debug-options-timber.html"&gt;surface stacktraces&lt;/a&gt; for quicker debugging, and how we can &lt;a href="https://zarah.dev/2019/07/08/debug-options-actions.html"&gt;retrieve Logcat traces out of our testers’ devices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for sticking with the series, and massive thanks to everyone who has given me their time helping get these posts through! 💚&lt;/p&gt;

</description>
      <category>android</category>
    </item>
    <item>
      <title>On-Device Debugging Part IV: Log All The Things!</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Mon, 08 Jul 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/on-device-debugging-part-iv-log-all-the-things-g27</link>
      <guid>https://dev.to/zmdominguez/on-device-debugging-part-iv-log-all-the-things-g27</guid>
      <description>&lt;p&gt;Over the past year, my team have been steadily building a Developer Options screen for our app. It is a simple &lt;a href="https://developer.android.com/reference/androidx/preference/PreferenceScreen.html"&gt;&lt;code&gt;PreferenceScreen&lt;/code&gt;&lt;/a&gt; available on debug builds that help us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;figure out what’s going on without needing to be attached to a computer&lt;/li&gt;
&lt;li&gt;test various configurations without re-installing&lt;/li&gt;
&lt;li&gt;have a host for various experimentations we are trying to explore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this series of posts, I will share what these various options are and how we made them.&lt;/p&gt;

&lt;p&gt;Read the other posts in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zarah.dev/2019/06/22/debug-options-toggles.html"&gt;Part I: Now It’s On, Now It’s Off&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zarah.dev/2019/06/24/debug-options-timber.html"&gt;Part II: Timbeeeeeeer!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zarah.dev/2019/07/01/debug-options-info.html"&gt;Part III: Inspect, Reset, Repeat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(If you are not familiar with &lt;a href="https://developer.android.com/reference/kotlin/androidx/preference/PreferenceFragmentCompat.html"&gt;&lt;code&gt;PreferenceFragmentCompat&lt;/code&gt;&lt;/a&gt;, I highly suggest to read about that first before proceeding. You can start with this &lt;a href="https://developer.android.com/guide/topics/ui/settings.html"&gt;AndroidX guide&lt;/a&gt; on Settings.)&lt;/p&gt;




&lt;p&gt;As I mentioned in &lt;a href="https://dev.to/zmdominguez/on-device-debugging-part-ii-timbeeeeeeer-1827"&gt;part II&lt;/a&gt;, we leverage Timber a lot. Those stacktrace log screens have been really useful when the app misbehaves, but what about those times when we just want to have checkpoints whilst using the app?&lt;/p&gt;

&lt;p&gt;Enter our next set of developer options:&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
&lt;a href="https://imgur.com/asPf70G"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eQ1U3gXN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/asPf70G.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;small&gt;More debugging!&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;
&lt;h3&gt;
  
  
  Show Debug Toasts
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.android.com/guide/topics/ui/notifiers/toasts"&gt;&lt;code&gt;Toast&lt;/code&gt;&lt;/a&gt;s are a great way of setting visual checkpoints in an app. They are relatively unobtrusive and easy to create.&lt;/p&gt;

&lt;p&gt;Enabling “Show debug Toasts” in our developer settings allows us to surface these checkpoints when we are testing something unpredictable:&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
&lt;a href="https://imgur.com/9us9Y6I"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UzISZrKb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/9us9Y6I.png%3F1" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;small&gt;&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;I previously talked about a geofencing feature we had to implement, and we have that feature to thank for spurning the idea for this debugging feature.&lt;/p&gt;

&lt;p&gt;In this feature, we want the app to notify the user when they enter a geofence. Testing geofences in the middle of the city can be tricky, and having visual cues that let us know if we have entered or exited a geofence has been super helpful for us.&lt;/p&gt;

&lt;p&gt;This function lives in our &lt;code&gt;DebugExtensions&lt;/code&gt; class:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;(In this case, &lt;code&gt;SettingsInteractor&lt;/code&gt; takes care of exposing some of the debug options that our main source set needs access to.)&lt;/p&gt;

&lt;p&gt;Having a developer option toggle to control this means we can turn the Toasts off when it gets too annoying, and we lower the risk of leaving a debugging Toast when we release to production as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable Leak Canary
&lt;/h3&gt;

&lt;p&gt;We have some really old Activities in the app that drive &lt;a href="https://github.com/square/leakcanary"&gt;Leak Canary&lt;/a&gt; crazy. Until we get around to cleaning them up, it also drives our QAs crazy. This toggle gives them the power to turn the tool off so they can test in peace.&lt;/p&gt;

&lt;p&gt;As a rule, developers keep this toggle on during active development. As for those old Activities, we are in the process of reworking them to make them perform better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Show Debug Notification
&lt;/h3&gt;

&lt;p&gt;When enabled, the app shows a persistent notification that serves as the shortcut to this Developer Settings screen (we also provided an entry to this screen in our app’s junk drawer aka the “More” menu for debug builds). Pretty straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logs
&lt;/h3&gt;

&lt;p&gt;When connected to our development computers, we have a wide array of debugging tools at our disposal. We can use &lt;code&gt;Timber&lt;/code&gt;, or &lt;code&gt;System.out&lt;/code&gt;, or &lt;code&gt;Toast&lt;/code&gt;s, or breakpoints, etc.&lt;/p&gt;

&lt;p&gt;But going back to our geofencing feature, all of these options are not really viable to us during testing. Sure we can walk around Surry Hills with a laptop and keep an eye on Logcat whilst debugging. But we really can’t expect everyone who is testing the app to do this.&lt;/p&gt;

&lt;p&gt;When this option is turned on, we send all the Logcat information from our app into a text file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Getting those logs out of a particular device can be fiddly, dealing with multiple OEMs and USB variations. And those logs are pretty useless just sitting there, so we put in the option to send those files to us:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Using &lt;a href="https://developer.android.com/training/sharing/send#send-multiple-content"&gt;&lt;code&gt;ACTION_SEND_MULTIPLE&lt;/code&gt;&lt;/a&gt; with the Intent allows us to automatically attach all the log files we can find in the user’s debug file storage.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
&lt;a href="https://imgur.com/VOIY4yq"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qeFzVPrd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/VOIY4yq.png%3F1" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;small&gt;Gimme those logs!&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;Depending on the email client they choose to send the files with, they can remove or add more files as they please.&lt;/p&gt;

&lt;p&gt;To be good citizens, we have also provided the option to clear all existing logs so that users can reclaim their precious storage space.&lt;/p&gt;

&lt;p&gt;Out of all the debugging features that we have, this one is probably my favourite. 💚&lt;/p&gt;

&lt;p&gt;(Massive thanks go out to &lt;a href="https://twitter.com/ataulm"&gt;@ataul&lt;/a&gt; for all his patience reviewing my crazy ramblings 😆 )&lt;/p&gt;




&lt;p&gt;In the next post, we will look at how our developer settings help us work better with the designers in our team. Stay tuned! 📻&lt;/p&gt;

</description>
      <category>android</category>
    </item>
    <item>
      <title>On-Device Debugging Part III: Inspect, Reset, Repeat</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Mon, 01 Jul 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/on-device-debugging-part-iii-inspect-reset-repeat-ocj</link>
      <guid>https://dev.to/zmdominguez/on-device-debugging-part-iii-inspect-reset-repeat-ocj</guid>
      <description>&lt;p&gt;Over the past year, my team have been steadily building a Developer Options screen for our app. It is a simple &lt;a href="https://developer.android.com/reference/androidx/preference/PreferenceScreen.html"&gt;&lt;code&gt;PreferenceScreen&lt;/code&gt;&lt;/a&gt; available on debug builds that help us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;figure out what’s going on without needing to be attached to a computer&lt;/li&gt;
&lt;li&gt;test various configurations without re-installing&lt;/li&gt;
&lt;li&gt;have a host for various experimentations we are trying to explore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this series of posts, I will share what these various options are and how we made them.&lt;/p&gt;

&lt;p&gt;Read the other posts in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zarah.dev/2019/06/22/debug-options-toggles.html"&gt;Part I: Now It’s On, Now It’s Off&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zarah.dev/2019/06/24/debug-options-timber.html"&gt;Part II: Timbeeeeeeer!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(If you are not familiar with &lt;a href="https://developer.android.com/reference/kotlin/androidx/preference/PreferenceFragmentCompat.html"&gt;&lt;code&gt;PreferenceFragmentCompat&lt;/code&gt;&lt;/a&gt;, I highly suggest to read about that first before proceeding. You can start with this &lt;a href="https://developer.android.com/guide/topics/ui/settings.html"&gt;AndroidX guide&lt;/a&gt; on Settings.)&lt;/p&gt;




&lt;p&gt;A lot of things affect how the Woolworths app behaves. Sometimes databases get out of sync between production and development environments, there are one-shot screens and tooltips, the UI may look different based on device qualities, or even changes to available options to users based on the type of account they have.&lt;/p&gt;

&lt;p&gt;To manage and keep track of all these things, we included a set of actions in our developer options.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/hyYqCEf"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6JjLKt4p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/hyYqCEf.png%3F5" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;small&gt;Additional actions&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;
&lt;h3&gt;
  
  
  Environment Switcher
&lt;/h3&gt;

&lt;p&gt;We have a few different environments that the apps can use. Having a switcher built into the app means we can easily test our app’s behaviour on any of the available environments.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/HvK6quk"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YAn8Jlrq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/HvK6quk.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;small&gt;Switcher confirmation&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;For us, switching environments mean revoking access tokens and everything else that come with that. Since this is a destructive action (they would need to log back in), we opted to present a confirmation dialog before proceeding.&lt;/p&gt;

&lt;p&gt;Your testers are users too, so UX is important. 😉&lt;/p&gt;
&lt;h3&gt;
  
  
  Inspect and Reset Preferences
&lt;/h3&gt;

&lt;p&gt;Last year, Woolworths have completely &lt;a href="https://www.woolworthsgroup.com.au/page/media/Latest_News/single-use-plastic-shopping-bags-gone-for-good-at-woolworths"&gt;removed single-use plastic bags&lt;/a&gt; from all stores nationwide. To help with the transition to reusable bags, we built a feature into the app that allows users to enable bag reminders for their chosen store. We also included a “nudge” feature to assist with discovery – the app would nudge you three times to set up your reminders, after which it assumes you do not want to set it up.&lt;/p&gt;

&lt;p&gt;This is one of the many use cases where we leveraged &lt;code&gt;SharedPreference&lt;/code&gt;s. This whole feature is based on geofencing around a store, which can be a little finicky. We decided to include a &lt;code&gt;SharedPreference&lt;/code&gt; inspector so that we can easily answer questions from our testers like “I did not get any set up notifications, I’m pretty sure I have only gotten two?”.&lt;/p&gt;

&lt;p&gt;Being able to see what current values have been saved in the app allows us to quickly triage issues in the wild.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
&lt;a href="https://imgur.com/rlVuxP7"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3_vvv4L7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/rlVuxP7.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;small&gt;Remember that override Firebase kill switch?&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;I have already written about resetting &lt;code&gt;SharedPreferences&lt;/code&gt; &lt;a href="https://zarah.dev/2018/11/16/reset-prefs.html"&gt;here&lt;/a&gt;, and the “inspect” bit follows the same principle as that. Mainly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This function assumes that the SharedPreferences files your app owns follow this naming convention: MY.APPLICATION.ID_my_preference_file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But we do need some modifications to the gist quoted in that post. For one, we can extract out the bit that reads the files:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We need to do a bit of processing because we want to have readable names (again, UX!) as well as the name that Android would need to retrieve the &lt;code&gt;SharedPreference&lt;/code&gt;. As an example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File location&lt;/th&gt;
&lt;th&gt;&lt;code&gt;/data/user/0/com.woolworths.debug/shared_prefs/com.woolworths.debug_prefs.xml&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Android-readable name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;com.woolworths.debug_prefs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nice name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;debug_prefs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To keep things simple, we only display a dialog with all the existing values in the &lt;code&gt;SharedPreference&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This option has helped us a lot debugging our nudges out in the wild. It eliminated the need to be connected to a computer, or to fiddle with file viewers, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Show Device Info
&lt;/h3&gt;

&lt;p&gt;Sometimes we want the UI to change based on some device qualifications like say the smallest available width. Using the emulator makes it super easy for us to test these visual breakpoints. But for someone who does not have access to the emulator, it can be quite hard to verify the UI changes we expect.&lt;/p&gt;

&lt;p&gt;This screen shows some basic information that might affect how our app displays something to the user:&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;a href="https://imgur.com/Qz5kEDi"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--74E15kd6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/Qz5kEDi.png%3F1" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;small&gt;Minimum information to debug device-dependent UI&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;Everything under the “Build Information” header is retrieved from Android’s &lt;code&gt;Build.VERSION&lt;/code&gt; API or the &lt;code&gt;BuildConfig&lt;/code&gt;. And for the “Device Information” section, we utilised &lt;code&gt;windowManager.defaultDisplay.getMetrics(DisplayMetrics())&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The “Account Information” section contains some basic information about the user that might affect the UI. For example, if “Is legal adult” is &lt;code&gt;false&lt;/code&gt;, the user will not be able to see any liquor products in the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Show Diagnostic Info
&lt;/h3&gt;

&lt;p&gt;There is a lot more information that affects a user’s experience with the Woolworths app other than if they are logged in or not. Inventory is different store-to-store, Rewards and offers may be targetted, legal requirements are different based on the state, among other things.&lt;/p&gt;

&lt;p&gt;All of these things may affect the app one way or another, and we use a Diagnostics screen to display all those information.&lt;/p&gt;

&lt;p&gt;And with that we wrap up this post on our developer options for UI-affecting variables. In the next post, we will talk about logging, Toasts, and more fun stuff! 🎊&lt;/p&gt;

</description>
      <category>android</category>
    </item>
    <item>
      <title>On-Device Debugging Part II: Timbeeeeeeer!</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Mon, 24 Jun 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/on-device-debugging-part-ii-timbeeeeeeer-1827</link>
      <guid>https://dev.to/zmdominguez/on-device-debugging-part-ii-timbeeeeeeer-1827</guid>
      <description>&lt;p&gt;Over the past year, my team have been steadily building a Developer Options screen for our app. It is a simple &lt;a href="https://developer.android.com/reference/androidx/preference/PreferenceScreen.html" rel="noopener noreferrer"&gt;&lt;code&gt;PreferenceScreen&lt;/code&gt;&lt;/a&gt; available on debug builds that aims to help us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;figure out what’s going on without needing to be attached to a computer&lt;/li&gt;
&lt;li&gt;test various configurations without re-installing&lt;/li&gt;
&lt;li&gt;have a host for various experimentations we are trying to explore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this series of posts, I will share what these various options are and how we made them.&lt;/p&gt;

&lt;p&gt;(If you are not familiar with &lt;a href="https://developer.android.com/reference/kotlin/androidx/preference/PreferenceFragmentCompat.html" rel="noopener noreferrer"&gt;&lt;code&gt;PreferenceFragmentCompat&lt;/code&gt;&lt;/a&gt;, I highly suggest to read about that first before proceeding. You can start with this &lt;a href="https://developer.android.com/guide/topics/ui/settings.html" rel="noopener noreferrer"&gt;AndroidX guide&lt;/a&gt; on Settings.)&lt;/p&gt;

&lt;p&gt;Read Part I (Now It’s On, Now It’s Off) &lt;a href="https://zarah.dev/2019/06/22/debug-options-toggles.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;One of the tools we utilise a lot at Woolworths is &lt;a href="https://github.com/JakeWharton/timber" rel="noopener noreferrer"&gt;Timber&lt;/a&gt;. We use it not just to help us with debugging during development, but also to figure out problems early during testing as well as to keep an eye on things in production.&lt;/p&gt;

&lt;p&gt;Before, when we have a build for testing and things don’t go to plan, we get sent a screenshot pretty much like this:&lt;/p&gt;

&lt;center&gt;
    &lt;a href="https://imgur.com/Bzleb1m" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FBzleb1m.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;Uh 🙀&lt;/small&gt;
&lt;/center&gt;

&lt;p&gt;We try to figure out what they were trying to do before the app crashed, replicate the exact conditions they had prior, and try and try and try. More often than not, we hit the “that shouldn’t have happened” wall – even though it obviously just did. 🤦‍♀️&lt;/p&gt;

&lt;p&gt;To help in these situations, we turned to Timber. We log those “We should never get here!!!” scenarios using &lt;code&gt;Timber.e&lt;/code&gt;. In production, those logs get sent to Fabric via &lt;code&gt;Crashlytics.log()&lt;/code&gt;. In debug builds, those logs get sent to a crash log screen.&lt;/p&gt;

&lt;p&gt;This screen shows the stacktrace, if there’s any, and some basic information about the build and the device. We also decided to use this screen to log any uncaught exceptions. At the bottom of the screen is a button to send the crash log through via an &lt;a href="https://developer.android.com/training/sharing/send" rel="noopener noreferrer"&gt;Intent Chooser&lt;/a&gt;.&lt;/p&gt;

&lt;center&gt;
    &lt;a href="https://imgur.com/StEAFvr" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FStEAFvr.png" title="source: imgur.com"&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;Crash log screen variations&lt;/small&gt;
&lt;/center&gt;

&lt;p&gt;To bring this to life, we override &lt;code&gt;Timber&lt;/code&gt;’s &lt;code&gt;e&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggingTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Timber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DebugTree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;vararg&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;printStackTrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="k"&gt;vararg&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;printStackTrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&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 &lt;code&gt;printStackTrace&lt;/code&gt; simply passes on the information to a dedicated &lt;code&gt;Activity&lt;/code&gt; that would display the error.&lt;/p&gt;

&lt;p&gt;We provide the kind of error as an &lt;code&gt;Intent&lt;/code&gt; extra with a &lt;code&gt;CrashType&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LogType&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;additionalMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;@ColorRes&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;UNCAUGHT_EXCEPTION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"!!! DO NOT IGNORE! THIS WILL CAUSE A CRASH IN PRODUCTION !!!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_color&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="nc"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"We are monitoring an issue. You may still send this log if you want."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The crash screen implementation is pretty straightforward, we retrieve the extras and use data binding to display the message and style the header. (For the curious, I previously wrote about &lt;a href="https://zarah.dev/2016/07/19/using-resource-ids-in-data-binding.html" rel="noopener noreferrer"&gt;using resource references in databinding&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Since we use the crash log screen for uncaught exceptions as well, we directly call the &lt;code&gt;Activity&lt;/code&gt; when something goes wrong. This means that it skips the &lt;code&gt;Timber&lt;/code&gt; processing but sometimes it’s still useful to see the logs in Logcat, so we log the stacktrace there as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SuppressLint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LogNotTimber"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="c1"&gt;// We still want to display the stack trace in Logcat for uncaught exceptions&lt;/span&gt;
    &lt;span class="c1"&gt;// but are not using Timber.e here lest we get into an infinite loop&lt;/span&gt;
    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FIXME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStackTraceString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stackTrace&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

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

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

&lt;/div&gt;



&lt;p&gt;The “Send crash” button at the bottom of the screen sends out an &lt;code&gt;ACTION_SEND&lt;/code&gt; &lt;code&gt;Intent&lt;/code&gt; so that users can send the information via email, Slack, or whatever other app they choose (I also wrote about that &lt;a href="https://zdominguez.com/2017/03/31/sharing-is-caring.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;!).&lt;/p&gt;

&lt;p&gt;This may seem like a simple thing, but time and again it has helped us quickly figure out issues in a more meaningful way. Even if those helping us test choose to send a screenshot, there is more relevant information rather than the generic crash dialog. 💞&lt;/p&gt;

</description>
      <category>android</category>
    </item>
    <item>
      <title>On-Device Debugging Part I: Now It’s On, Now It’s Off</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Sat, 22 Jun 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/on-device-debugging-part-i-now-it-s-on-now-it-s-off-2bbe</link>
      <guid>https://dev.to/zmdominguez/on-device-debugging-part-i-now-it-s-on-now-it-s-off-2bbe</guid>
      <description>&lt;p&gt;Over the past year, my team have been steadily building a Developer Options screen for our app. It is a simple &lt;a href="https://developer.android.com/reference/androidx/preference/PreferenceScreen.html"&gt;&lt;code&gt;PreferenceScreen&lt;/code&gt;&lt;/a&gt; available on debug builds that aims to help us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;figure out what’s going on without needing to be attached to a computer&lt;/li&gt;
&lt;li&gt;test various configurations without re-installing&lt;/li&gt;
&lt;li&gt;have a host for various experimentations we are trying to explore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this series of posts, I will share what these various options are and how we made them.&lt;/p&gt;

&lt;p&gt;(If you are not familiar with &lt;a href="https://developer.android.com/reference/kotlin/androidx/preference/PreferenceFragmentCompat.html"&gt;&lt;code&gt;PreferenceFragmentCompat&lt;/code&gt;&lt;/a&gt;, I highly suggest to read about that first before proceeding. You can start with this &lt;a href="https://developer.android.com/guide/topics/ui/settings.html"&gt;AndroidX guide&lt;/a&gt; on Settings.)&lt;/p&gt;




&lt;p&gt;At Woolworths, we use &lt;a href="https://en.wikipedia.org/wiki/Feature_toggle"&gt;feature flags&lt;/a&gt; a lot. It helps us merge our code to master quicker, allows our QA team to provide feedback faster, and helps manage the risks when doing an important release.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://firebase.google.com/docs/remote-config"&gt;Firebase Remote Config&lt;/a&gt; to manage our existing feature flags, which enable us and our product owners to control these flags very easily.&lt;/p&gt;

&lt;p&gt;Although it is very convenient to have these flags configurable remotely, it also makes it difficult to do a quick test on something behind a feature flag for someone who doesn’t have access to the console. It might also disrupt someone’s work if somebody else changes the flags’ values remotely without them knowing it.&lt;/p&gt;

&lt;p&gt;It is for this reason that we introduced local feature toggles. This gives anyone on a debug build the ability to turn as many features on or off as many times as they want without affecting anyone else’s builds.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
    &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qmna2ae0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/tJWVINR.jpg"&gt;&lt;br&gt;
    &lt;br&gt;&lt;br&gt;
&lt;small&gt;Names changed to protect individuals 😅&lt;/small&gt;&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;On a fresh install, the app would use the &lt;a href="https://firebase.google.com/docs/remote-config/use-config-android#set-in-app-default-parameter-values"&gt;in-app default values&lt;/a&gt; we have set and the override switch is OFF. The app would keep using the values provided by Firebase until the override switch is flipped.&lt;/p&gt;

&lt;p&gt;And when it gets confusing and we want a do-over, the “Reset local to Firebase” button would turn the flags back to what is provided by Firebase.&lt;/p&gt;


&lt;center&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GCthSVlU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i.imgur.com/Bz3DVdd.gif"&gt;&lt;/center&gt;

&lt;p&gt;All the features in this list are added dynamically from an enum that holds all our feature flags:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;There a few things at work to make all of this come together. First off, if the override button is OFF, we want everything related to feature flags to be disabled. This gives better clarity on what settings the app is using, and prevents the user from unwittingly changing a value.&lt;/p&gt;

&lt;p&gt;To do this, we define the switch’s behaviour in our XML file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The attribute &lt;code&gt;disableDependentsState&lt;/code&gt; means that if this switch is OFF, any other value that depends on this preference gets disabled.&lt;/p&gt;

&lt;p&gt;Next, we want to create a new category that will contain all of our feature toggles and belong to the same category as our override switch.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We can then add each of our features into this &lt;code&gt;featureTogglesCategory&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;For simplicity, we use the &lt;code&gt;Feature&lt;/code&gt; enum’s name as the &lt;code&gt;SharedPreference&lt;/code&gt; key for each feature toggle, as well as the label that appears beside the &lt;code&gt;Switch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What the function &lt;code&gt;isFeatureEnabled()&lt;/code&gt; does is simply return Firebase-provided values for production builds, and figure out what the our preferences are for debug builds:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now that we have prepared our feature flags &lt;code&gt;PreferenceCategory&lt;/code&gt;, we can route our dependency on our override switch:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;It may seem turned the other way around, but that is correct. ✅ To set this new category as &lt;em&gt;dependent ON another preference&lt;/em&gt;, we need to call &lt;a href="https://developer.android.com/reference/kotlin/androidx/preference/Preference.html#setDependency(kotlin.String)"&gt;&lt;code&gt;setDependency()&lt;/code&gt;&lt;/a&gt; and give it the name of that control preference.&lt;/p&gt;

&lt;p&gt;As for our “Revert…” button, we also want it to be disabled if the override switch is off (because it doesn’t serve any real purpose in this case):&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And to handle user clicks on this button, we set up a callback in our &lt;code&gt;PreferenceFragment&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And that’s it! We have dynamically added a set of switches to our &lt;code&gt;PreferenceScreen&lt;/code&gt; that would hopefully make testing our app much easier.&lt;/p&gt;

&lt;p&gt;In the next post in the series, we will talk about more on-device debugging tools such as managing our &lt;code&gt;SharedPreferences&lt;/code&gt;, and showing device information, among others.&lt;/p&gt;

</description>
      <category>android</category>
    </item>
    <item>
      <title>Tintable Toolbar Things</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Fri, 21 Jun 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/tintable-toolbar-things-19b6</link>
      <guid>https://dev.to/zmdominguez/tintable-toolbar-things-19b6</guid>
      <description>&lt;p&gt;A few weeks ago, I merged a pull request that updates our app’s theme to Material Components &lt;a href="https://material.io/develop/android/docs/getting-started/" rel="noopener noreferrer"&gt;from the Bridge version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1135432513688465408-366" src="https://platform.twitter.com/embed/Tweet.html?id=1135432513688465408"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1135432513688465408-366');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1135432513688465408&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;I have been referencing the &lt;a href="https://github.com/material-components/material-components-android/tree/master/catalog" rel="noopener noreferrer"&gt;MDC Catalog&lt;/a&gt; app a lot during this exercise; and I thought it would be a good idea to pattern how we organise and apply our theming and styling based on that app. (Hey they must know what they’re doing, right?)&lt;/p&gt;

&lt;p&gt;We have two kinds of Toolbars in our app – the first one with our standard primary colour, and a “clean” variant.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/tintables/toolbar_types.jpg"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzdominguez.com%2Fassets%2Ftintables%2Ftoolbar_types.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we initially set up our theming, we made this theme overlay (read &lt;a href="https://medium.com/androiddevelopers/theming-with-appcompat-1a292b754b35" rel="noopener noreferrer"&gt;about theme overlays here&lt;/a&gt;) to style our toolbar and give it that green arrow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;style&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThemeOverlay.Toolbar.Inverse"&lt;/span&gt; &lt;span class="na"&gt;parent=&lt;/span&gt;&lt;span class="s"&gt;"ThemeOverlay.AppCompat.Light"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"colorControlNormal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;?colorAccent&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android:background"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;@color/white&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We then set this in our Toolbars as &lt;code&gt;android:theme&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;androidx.appcompat.widget.Toolbar&lt;/span&gt; 
    &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;android:theme=&lt;/span&gt;&lt;span class="s"&gt;"@style/ThemeOverlay.Toolbar.Inverse"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The MDC Catalog app uses &lt;strong&gt;&lt;em&gt;styles&lt;/em&gt;&lt;/strong&gt; instead of &lt;strong&gt;&lt;em&gt;themes&lt;/em&gt;&lt;/strong&gt; , so I set about copying &lt;a href="https://github.com/material-components/material-components-android/blob/master/catalog/java/io/material/catalog/application/theme/res/values/styles.xml#L19" rel="noopener noreferrer"&gt;their technique&lt;/a&gt;. (Side note: Anita Singh has an &lt;a href="https://speakerdeck.com/anitas3791/styles-themes-material-theming-oh-my" rel="noopener noreferrer"&gt;amazing talk on styles, themes, and material design&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;We immediately run into one problem: in our theme-based approach, we can set the &lt;code&gt;colorControlNormal&lt;/code&gt; attribute in our theme overlay, but we cannot do that anymore with our style-based approach.&lt;/p&gt;

&lt;p&gt;We definitely want our green arrow though, and we can look at the MDC source code to get some clues. It’s always a good idea to start off with what we want to be our &lt;a href="https://github.com/material-components/material-components-android/blob/8f622283d18466620a280f6f6bbb32fafb157efd/lib/java/com/google/android/material/appbar/res/values/styles.xml#L65" rel="noopener noreferrer"&gt;parent style&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;style&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Widget.MaterialComponents.Toolbar.Surface"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android:background"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;?attr/colorSurface&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"titleTextColor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;?attr/colorOnSurfaceEmphasisHighType&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"subtitleTextColor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;?attr/colorOnSurfaceEmphasisMedium&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Note: this theme overlay will only work if the style is applied directly to a Toolbar. --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android:theme"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;@style/ThemeOverlay.MaterialComponents.Toolbar.Surface&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;🤔 Interesting note there, we should totally remember that.&lt;/p&gt;

&lt;p&gt;Taking a peek at the &lt;a href="https://github.com/material-components/material-components-android/blob/8f622283d18466620a280f6f6bbb32fafb157efd/lib/java/com/google/android/material/appbar/res/values/styles.xml#L78" rel="noopener noreferrer"&gt;referenced theme overlay&lt;/a&gt;, we see the attribute we want 🎉 :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;style&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThemeOverlay.MaterialComponents.Toolbar.Surface"&lt;/span&gt; &lt;span class="na"&gt;parent=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"colorControlNormal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;?attr/colorOnSurfaceEmphasisMedium&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"actionMenuTextColor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;?attr/colorOnSurfaceEmphasisMedium&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Adapting this to our code, we now have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;style&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Widget.Toolbar.Inverse"&lt;/span&gt; &lt;span class="na"&gt;parent=&lt;/span&gt;&lt;span class="s"&gt;"Widget.MaterialComponents.Toolbar.Surface"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android:background"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;@color/white&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"android:theme"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;@style/ThemeOverlay.ToolbarTint&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThemeOverlay.ToolbarTint"&lt;/span&gt; &lt;span class="na"&gt;parent=&lt;/span&gt;&lt;span class="s"&gt;"ThemeOverlay.MaterialComponents.Toolbar.Surface"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"colorControlNormal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;?colorSecondary&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And applying this to our Toolbar (remember it is now a &lt;strong&gt;&lt;em&gt;style&lt;/em&gt;&lt;/strong&gt;!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt; &lt;span class="nt"&gt;&amp;lt;androidx.appcompat.widget.Toolbar&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"@style/Widget.Toolbar.Inverse"&lt;/span&gt;
    &lt;span class="na"&gt;app:navigationIcon=&lt;/span&gt;&lt;span class="s"&gt;"?homeAsUpIndicator"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And this works really well, until….&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/tintables/no_x.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzdominguez.com%2Fassets%2Ftintables%2Fno_x.png"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;center&gt;&lt;small&gt;The close button is now invisible 😱&lt;/small&gt;&lt;/center&gt;

&lt;p&gt;The only difference between this screen and the others is that we provide a vector drawable for the navigation icon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;androidx.appcompat.widget.Toolbar&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"@style/Widget.Toolbar.Inverse"&lt;/span&gt;
    &lt;span class="na"&gt;app:navigationContentDescription=&lt;/span&gt;&lt;span class="s"&gt;"@string/close"&lt;/span&gt;
    &lt;span class="na"&gt;app:navigationIcon=&lt;/span&gt;&lt;span class="s"&gt;"@drawable/ic_close"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;So it &lt;strong&gt;should&lt;/strong&gt; work, right? MDC Catalog has a &lt;a href="https://github.com/material-components/material-components-android/blob/2de39fafe0285aab7e6e101549c4bc93f184a7e5/catalog/java/io/material/catalog/application/theme/res/values/styles.xml#L21" rel="noopener noreferrer"&gt;style with a close button&lt;/a&gt; like we have, the vector they use also has a &lt;code&gt;fillColor&lt;/code&gt; hardcoded in it (I even tried making their fill colour red 😂), but it is always always getting tinted correctly.&lt;/p&gt;

&lt;p&gt;I always find theming and styling really hard to debug, so I gave up on this for a while and conceded my defeat. The next day, I decided to timebox myself to a couple of hours – if I can’t figure it out by then I will just stick to how it was before. I re-read all the blog posts, re-reviewed the source code, and gave StackOverflow another shot.&lt;/p&gt;

&lt;p&gt;And there… in &lt;a href="https://stackoverflow.com/questions/28219178/toolbar-icon-tinting-on-android#comment76399857_38650854" rel="noopener noreferrer"&gt;one random comment exchange&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What’s the difference between using ?attr/colorControlNormal at android:tint vs android:fillColor attributes? Thanks! – Thomas Vos Jun 20 ‘17 at 15:46 @SuperThomasLab The example is from Chris Banes’s blog where it illustrates replacing a tinted image with a tinted vector. Note that the fillColor was effectively hard-coded in the source image. I do not know if other color representations are now supported in AppCompat. – Joe Bowbeer Jun 22 ‘17 at 20:06&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And sure enough, our vector didn’t have &lt;code&gt;android:tint&lt;/code&gt; defined in it! 🤦‍♀️ Adding it into our vector makes everything work perfectly 👌&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;vector&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
    &lt;span class="na"&gt;android:width=&lt;/span&gt;&lt;span class="s"&gt;"24dp"&lt;/span&gt;
    &lt;span class="na"&gt;android:height=&lt;/span&gt;&lt;span class="s"&gt;"24dp"&lt;/span&gt;
    &lt;span class="na"&gt;android:viewportWidth=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt;
    &lt;span class="na"&gt;android:viewportHeight=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt;
    &lt;span class="na"&gt;android:tint=&lt;/span&gt;&lt;span class="s"&gt;"?colorControlNormal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt;
      &lt;span class="na"&gt;android:fillColor=&lt;/span&gt;&lt;span class="s"&gt;"#FFFFFFFF"&lt;/span&gt;
      &lt;span class="na"&gt;android:pathData=&lt;/span&gt;&lt;span class="s"&gt;"M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/vector&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I know it may be obvious to some people (&lt;em&gt;of course&lt;/em&gt; you need a tint to tint something, duh???), but it wasn’t super obvious to me.&lt;/p&gt;

&lt;p&gt;So, TL;DR: Make sure your vectors &lt;em&gt;actually&lt;/em&gt; support tinting before trying to tint it. 🌈&lt;/p&gt;

</description>
      <category>android</category>
    </item>
    <item>
      <title>Shortcuts to Shortcuts</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Sat, 08 Jun 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/shortcuts-to-shortcuts-572d</link>
      <guid>https://dev.to/zmdominguez/shortcuts-to-shortcuts-572d</guid>
      <description>&lt;p&gt;It has been a few years since I last looked at implementing &lt;a href="https://developer.android.com/guide/topics/ui/shortcuts"&gt;app shortcuts&lt;/a&gt;, and lately I have been looking at them again. I remember implementing them the first time they were released for Android N, but as with life, things have changed a bit.&lt;/p&gt;

&lt;p&gt;In this post I want to share some interesting learnings I had whilst implementing static app shortcuts. (Note: This post assumes the reader is familiar with the &lt;a href="https://developer.android.com/guide/topics/ui/shortcuts/creating-shortcuts"&gt;basic requirements of implementing app shortcuts&lt;/a&gt;). As always, &lt;a href="https://github.com/zmdominguez/sdk_sandbox/tree/feature/app-shortcuts"&gt;all the code&lt;/a&gt; for this post is in my Sandbox App.&lt;/p&gt;

&lt;h3&gt;
  
  
  Android Studio support is… limited.
&lt;/h3&gt;

&lt;p&gt;☝️ For this exercise, I am using Android Studio 3.5 Beta 4.&lt;/p&gt;

&lt;p&gt;And by limited I mean resource references do not autocomplete at all. This becomes tricky as some of the required attributes &lt;em&gt;can&lt;/em&gt; use resource references. The table below shows what those are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Allowed&lt;/th&gt;
&lt;th&gt;Not allowed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shortcutShortLabel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shortcutId&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shortcutLongLabel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Intent&lt;/code&gt; attributes (&lt;code&gt;targetPackage&lt;/code&gt;, &lt;code&gt;targetClass&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;icon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;⚠️ My non-scientific test shows that on Pixel launchers, &lt;code&gt;shortcutShortLabel&lt;/code&gt; is used unless &lt;code&gt;shortcutLongLabel&lt;/code&gt; is 17 characters or less.&lt;/p&gt;

&lt;h3&gt;
  
  
  There are changes in design guidelines between APIs 25 and 26
&lt;/h3&gt;

&lt;p&gt;Android O introduced &lt;a href="https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive.html"&gt;adaptive icons&lt;/a&gt; and if we take into consideration that users can pin shortcuts onto their homescreens, we need to support this format if we want to provide the best possible user experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@crafty"&gt;Nick Butcher&lt;/a&gt; talked about this in detail in his &lt;a href="https://medium.com/androiddevelopers/implementing-adaptive-icons-1e4d1795470e"&gt;post on implementing&lt;/a&gt; adaptive icons.&lt;/p&gt;

&lt;p&gt;⚠️ Using a &lt;code&gt;VectorDrawable&lt;/code&gt; for the icon foreground as Nick showed, it didn’t seem to like using theme references for the fill colour. Using a resource reference is okay. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BghyG093--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/GvfiOMX.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BghyG093--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/GvfiOMX.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read &lt;a href="https://medium.com/google-design/designing-adaptive-icons-515af294c783"&gt;this post&lt;/a&gt; to learn more about designing adaptive icons, and &lt;a href="https://commondatastorage.googleapis.com/androiddevelopers/shareables/design/app-shortcuts-design-guidelines.pdf"&gt;this document&lt;/a&gt; for designing app shortcut icons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mo’ package names, mo’ files
&lt;/h3&gt;

&lt;p&gt;For each app shortcut we want to support, there needs to be an &lt;code&gt;Intent&lt;/code&gt; associated with it; with each &lt;code&gt;Intent&lt;/code&gt; needing a &lt;code&gt;targetPackage&lt;/code&gt; and a &lt;code&gt;targetClass&lt;/code&gt;. To wit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;intent
    android:action="android.intent.action.VIEW"
    android:targetPackage="com.zdominguez.sdksandbox"
    android:targetClass="com.zdominguez.sdksandbox.MainActivity" /&amp;gt;

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



&lt;p&gt;This might present a problem if your app have different package names for each build type. Someone has &lt;a href="https://stackoverflow.com/a/44840413/395576"&gt;written a function&lt;/a&gt; to support variables but unfortunately it does not work on more modern versions of the Gradle Plugin.&lt;/p&gt;

&lt;p&gt;This means that we would need multiple &lt;code&gt;shortcuts.xml&lt;/code&gt; files for all build variants that we want to support:&lt;br&gt;&lt;br&gt;
 &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cDbXQ2lW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/g3M5YgK.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cDbXQ2lW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/g3M5YgK.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Passing intent extras is possible, but sometimes a trampoline activity comes in handy
&lt;/h3&gt;

&lt;p&gt;If we need to pass some extras to the &lt;code&gt;Activity&lt;/code&gt; we are targetting, we can do so via the XML file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;intent ...&amp;gt;
    &amp;lt;extra android:name="target_tab" android:value="settings" /&amp;gt;
&amp;lt;/intent&amp;gt;

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



&lt;p&gt;One important statement that is a bit buried in the documentation for app shortcuts is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[W]hen the app is already running, all the existing activities in your app are destroyed when a static shortcut is launched.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is when a trampoline &lt;code&gt;Activity&lt;/code&gt; becomes useful so we can prepare our back stack or even get a handle on some requirements the receiving activity would need (for example, a value to be read from the database). You can read more about trampoline activities &lt;a href="https://developer.android.com/guide/topics/ui/shortcuts/managing-shortcuts#trampoline"&gt;here&lt;/a&gt;. ➰&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling up navigation
&lt;/h3&gt;

&lt;p&gt;Similar to how we should &lt;a href="https://developer.android.com/training/notify-user/navigation#define_your_apps_activity_hierarchy"&gt;handle navigation&lt;/a&gt; when starting an &lt;code&gt;Activity&lt;/code&gt; from a notification, we need to consider how the app would behave when the user navigates away from our target &lt;code&gt;Activity&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thus, we need to provide multiple &lt;code&gt;Intent&lt;/code&gt;s to the shortcut and the last one in the list would be what the user initially sees.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;shortcut ...&amp;gt;
    &amp;lt;intent
        android:action="android.intent.action.VIEW"
        android:targetPackage="com.zdominguez.sdksandbox"
        android:targetClass="com.zdominguez.sdksandbox.MainActivity"&amp;gt;
        &amp;lt;extra android:name="target_tab" android:value="settings" /&amp;gt;
    &amp;lt;/intent&amp;gt;
    &amp;lt;intent
        android:action="android.intent.action.VIEW"
        android:targetPackage="com.zdominguez.sdksandbox"
        android:targetClass="com.zdominguez.sdksandbox.DemoActivity"&amp;gt;
    &amp;lt;/intent&amp;gt;
&amp;lt;/shortcut&amp;gt;

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



&lt;p&gt;It took me a bit longer than I liked to figure out all of this 😩, so I’m writing it down to have a reference for future me.&lt;/p&gt;

&lt;p&gt;👯 Make sure to read the rest of the &lt;a href="https://developer.android.com/guide/topics/ui/shortcuts/best-practices"&gt;best practices&lt;/a&gt; document to learn more.&lt;/p&gt;

</description>
      <category>android</category>
    </item>
    <item>
      <title>Moving On Up (Or Down)</title>
      <dc:creator>Zarah Dominguez</dc:creator>
      <pubDate>Wed, 03 Apr 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/zmdominguez/moving-on-up-or-down-k13</link>
      <guid>https://dev.to/zmdominguez/moving-on-up-or-down-k13</guid>
      <description>&lt;center&gt;
&lt;br&gt;
&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media ltag__twitter-tweet__media__video-wrapper"&gt;
        &lt;div class="ltag__twitter-tweet__media--video-preview"&gt;
          &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A109FNyD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/tweet_video_thumb/D3MGtFoUIAAeObm.jpg" alt="unknown tweet media content"&gt;
          &lt;img src="/assets/play-butt.svg" class="ltag__twitter-tweet__play-butt" alt="Play butt"&gt;
        &lt;/div&gt;
        &lt;div class="ltag__twitter-tweet__video"&gt;
          
            
          
        &lt;/div&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--QAVeKriA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/790815005901922304/mBP_2hRs_normal.jpg" alt="Zarah Dominguez 🦉 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Zarah Dominguez 🦉
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @zarahjutz
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P4t6ys1m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Using CMD+SHIFT+UP/DOWN when reordering element in an enum respects the semicolon. 😍 It's the small things! 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      00:52 AM - 03 Apr 2019
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1113242947460325376" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1113242947460325376" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      17
      &lt;a href="https://twitter.com/intent/like?tweet_id=1113242947460325376" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      129
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;I tweeted that this morning after rediscovering CMD+SHIFT+UP. I have known about this shortcut for a while, but I guess I haven’t had reason to use it until lately.&lt;/p&gt;

&lt;p&gt;And then I remembered a variation of this shortcut that would literally “move things don’t care”.&lt;/p&gt;


&lt;center&gt;
&lt;br&gt;
&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media ltag__twitter-tweet__media__video-wrapper"&gt;
        &lt;div class="ltag__twitter-tweet__media--video-preview"&gt;
          &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wBUFKIcL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/tweet_video_thumb/D3M60E5V4AAKMSQ.jpg" alt="unknown tweet media content"&gt;
          &lt;img src="/assets/play-butt.svg" class="ltag__twitter-tweet__play-butt" alt="Play butt"&gt;
        &lt;/div&gt;
        &lt;div class="ltag__twitter-tweet__video"&gt;
          
            
          
        &lt;/div&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--QAVeKriA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/790815005901922304/mBP_2hRs_normal.jpg" alt="Zarah Dominguez 🦉 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Zarah Dominguez 🦉
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @zarahjutz
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P4t6ys1m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      EDIT: (versus SHIFT+ALT+UP/DOWN which just moves the line regardless of context) 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      04:32 AM - 03 Apr 2019
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1113298274838929408" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1113298274838929408" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      0
      &lt;a href="https://twitter.com/intent/like?tweet_id=1113298274838929408" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      9
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;/center&gt;

&lt;p&gt;This being IntelliJ, these actions work on more than just properties. They work for basically anything in your code that has a scope – individual lines, functions, classes even!&lt;/p&gt;

&lt;p&gt;There are two main movement actions, &lt;em&gt;move statement&lt;/em&gt; and &lt;em&gt;move line&lt;/em&gt;. Depending on what you want to do, each of these have their own benefits.&lt;/p&gt;

&lt;p&gt;Moving a &lt;em&gt;statement&lt;/em&gt; means that the IDE will respect the context within which that particular element exists. In the case of a single line of code, this means maintaining its current scope. What this means exactly is – if a line is inside a function, using CMD+SHIFT+UP/DOWN will &lt;em&gt;never&lt;/em&gt; move it out of the function.&lt;/p&gt;

&lt;p&gt;To wit:&lt;/p&gt;

&lt;p&gt;&lt;a href="///assets/moving_up/cmd_shift_line.gif"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uXJcQ1WZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://zdominguez.com/assets/moving_up/cmd_shift_line.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But this line wants to move to another function! It says so itself! This is when CMD+ALT+UP/DOWN comes in handy:&lt;/p&gt;

&lt;p&gt;&lt;a href="///assets/moving_up/cmd_alt_line_2.gif"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jWx-WiwJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://zdominguez.com/assets/moving_up/cmd_alt_line_2.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This shortcut is really handy when reordering or refactoring functions:&lt;/p&gt;

&lt;p&gt;&lt;a href="///assets/moving_up/cmd_shift_fun.gif"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WJXCV0xN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://zdominguez.com/assets/moving_up/cmd_shift_fun.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I leave it as an exercise for the reader to see how the shortcut behaves with classes. Get moving and stop copy-pasting! 👯&lt;/p&gt;

</description>
      <category>android</category>
    </item>
  </channel>
</rss>
