<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ken Yee on Medium]]></title>
        <description><![CDATA[Stories by Ken Yee on Medium]]></description>
        <link>https://medium.com/@kenkyee?source=rss-9548dc0b14c8------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*xzm71_uKGSYfRwkr.jpeg</url>
            <title>Stories by Ken Yee on Medium</title>
            <link>https://medium.com/@kenkyee?source=rss-9548dc0b14c8------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 22 Jun 2026 21:09:30 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@kenkyee/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Android SDKs and Obfuscation Best Practice]]></title>
            <link>https://kenkyee.medium.com/android-sdks-and-obfuscation-best-practice-b9e4933a28c4?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/b9e4933a28c4</guid>
            <category><![CDATA[sdk]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[proguard]]></category>
            <category><![CDATA[obfuscation]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Wed, 21 Feb 2024 15:05:10 GMT</pubDate>
            <atom:updated>2024-02-21T15:05:10.691Z</atom:updated>
            <content:encoded><![CDATA[<p>Spent a week debugging an interesting Proguard/Dexguard issue related to obfuscation and SDKs. It’s best practice to run your device tests against your app APK that is as obfuscated as much as possible so you can discover obfuscation issues before it’s released to the app store. But what if your SDK is also obfuscated with the same classnames?</p><p>During a device test, the app APK and test APKs classes use the same classloader, so you’ll have class name collisions. What’s more, depending on the order, the test APK’s classes may be in front of the app APK’s. This is what the root cause of the issue of our tests randomly crashing because some field was not not overridable. The app APK and an obfuscated SDK we used in the test APK had the same obfuscated classpath 🤯</p><p>Proguard/Dexguard defaults to putting all the obfuscated classes in the package “o”. However, you can change this using this configuration parameter in your proguard configuration file:</p><pre>-repackageclasses &#39;x&#39;</pre><p>So for all SDK authors out there: shadowjar’ing dependencies and obfuscating the SDK is a good idea. Just be sure to rename your default obfuscation package name so something other than the default! 🙂</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b9e4933a28c4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Architect vs. TL Staff]]></title>
            <link>https://kenkyee.medium.com/architect-vs-tl-staff-caeb3ad9d307?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/caeb3ad9d307</guid>
            <category><![CDATA[architects]]></category>
            <category><![CDATA[staff-engineer]]></category>
            <category><![CDATA[career-paths]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Sun, 01 Jan 2023 03:11:42 GMT</pubDate>
            <atom:updated>2023-01-01T03:11:42.298Z</atom:updated>
            <content:encoded><![CDATA[<p>Another year ends and it seems like it’s time for another introspective (a previous one can be found <a href="https://medium.com/@kenkyee/introspective-on-being-an-architect-tech-lead-team-lead-9bafef324d7d">here</a> about my time at KAYAK).</p><p>Since I wrote that blog, I joined Wayfair’s Core Platform Team as an Architect and what I did was very similar to what I did at KAYAK (short summary of Wayfair work <a href="https://medium.com/@kenkyee/predictions-goals-past-and-future-1633fbb92a68">in a previous blog</a>).</p><p>After Wayfair, I spent the last year at Twitter as a TL Staff Engineer (before Musk decimated it) and it was different enough that it’s worth describing how it’s a bit different. The great site <a href="https://staffeng.com">staffeng.com</a> has a good description of <a href="https://staffeng.com/guides/what-do-staff-engineers-actually-do">what staff engineers do</a>, as well as the different <a href="https://staffeng.com/guides/staff-archetypes">archetypes</a> of staff engineers.</p><p>Staff engineers are generally roles at larger companies and their primary tasks usually intersect with an architect’s tasks:</p><ul><li>Helping set technical direction and engineering perspective —a Staff+ person’s broad and/or deep experience helps immensely with this.</li><li>Mentorship/Sponsorship — Staff+ roles almost always share this responsibility with engineering managers in helping grow the developers in the rest of the company. Mentorship can be done via a more formal 1:1 mentorship program or just via helping out when folks have problems in Slack or via code reviews. Sponsorship requires being aware of what people do throughout the org.</li><li>Exploration — is something shared with architect roles; this means exploring new technologies to see if they will help improve the org.</li><li>Glue — this is a fairly broad term and just means taking care of everything that might fall through the cracks (similar to the “app janitor” tasks I mentioned in previous blogs).</li><li>Tech Lead Archetype — this type of Staff engineer is a partial mix of engineering manager and product manager because the work is an intersection of the work those roles traditionally do and they usually help lead a team or multiple teams.</li></ul><p>To elaborate a bit on the TL Staff aspects that I had to do at Twitter, this included tasks like:</p><ul><li>Helping define quarterly goals and OKRs.</li><li>Running the JIRA board/sprints to track if we were going to hit the goals.</li><li>Helping mentor a junior member of the the team.</li><li>Helping out another team (that was missing a TL) clean up their JIRA board and refocus on higher priority issues.</li><li>Helping with the PIP process of a team member.</li><li>Taking care of most of the firefighting issues that would have distracted a team member from focusing on their large project spanning multiple quarters so they could get put up for a promotion.</li><li>Exploring and building out a new way to run sandboxes to help improve the developer experience (these were mini-apps that let developers only load the portion of the project they needed to work on).</li><li>Prototyping ways to map code to teams so we could create/track metrics for teams.</li><li>Communicating/synchronizing with other teams for tooling for the scalable codebases modularization effort.</li></ul><p>As you can see, the TL Staff Eng work is very similar to what Architect roles generally do except for the PM/EM intersection. Time spent coding vs. communicating/collaborating is similar as well, with the TL role using a bit more time for the EM/PM work.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=caeb3ad9d307" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MVP/MVC to Reactive Architectures for Jetpack Compose]]></title>
            <link>https://kenkyee.medium.com/mvp-mvc-to-reactive-architectures-for-jetpack-compose-52502e49500f?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/52502e49500f</guid>
            <category><![CDATA[mvvm]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Sat, 08 Jan 2022 19:13:48 GMT</pubDate>
            <atom:updated>2022-01-08T20:32:06.276Z</atom:updated>
            <content:encoded><![CDATA[<p>For any Android application that’s more than five years old, this will probably be a common issue as developers try to integrate Jetpack Compose. Google, prior to the release of the Android Architecture Components in 2017, didn’t have an opinionated/recommended architecture. As a result, people writing applications copy/pasted examples from the Android documentation website and used this as “architecture”; the examples at the time were not as good as the ones today, so people ended up putting a lot of business logic in their Fragments and Activities. This was a pseudo MVC (Model -View-Controller) architecture with the Fragment/Activity being the “Controller”, the XML layout being the “View”, and any data classes were the “Model” (DB/network interactions were sometimes in the model and sometimes in the view depending on how developers interpreted the Android documentation). Applications with MVC tended to look like <a href="https://www.codementor.io/@dragneelfps/implementing-mvc-pattern-in-android-with-kotlin-i9hi2r06c">this</a>:</p><figure><img alt="Android MVC structure from https://www.codementor.io/@dragneelfps/implementing-mvc-pattern-in-android-with-kotlin-i9hi2r06c" src="https://cdn-images-1.medium.com/max/1024/0*Fq8AEbjcpuev6PaK" /></figure><p>Folks who did realize that putting business logic in Fragments/Activities made it hard to test (you usually needed <a href="http://robolectric.org/">Robolectric</a>), ended up using an MVP (Model-View-Presenter) model (and in some cases, they broke large Presenters down to use cases and interactors or adopted something like the iOS <a href="https://www.objc.io/issues/13-architecture/viper/">VIPER</a> model). The “View” was an interface that was defined against a Fragment/Activity. The “Model” stayed almost the same but also was where the DB/network access happened consistently. The business logic ended up in the Presenter and the View would call the Presenter and the Presenter would call the View to update the model changes into the view; the Presenter would also handle Android lifecycles with onStart/onDestroy() methods called by the view. MVP was a very calllback-oriented architecture. This is a good <a href="https://github.com/MindorksOpenSource/android-mvp-interactor-architecture">summary</a> of what MVP looked like in larger apps:</p><figure><img alt="Typical MVP structure From https://github.com/MindorksOpenSource/android-mvp-interactor-architecture" src="https://cdn-images-1.medium.com/max/1024/0*UDFrSGoaYGY4H5N2" /></figure><p>Unfortunately, these older MVC/MVP architectures are not a natural fit for Reactive UI like Jetpack Compose. The Presenter just ends up being an almost empty data relay middleman which doesn’t have any real logic.</p><h3>Architectures that Work Best with Reactive UI</h3><p>Reactive UIs (ReactJS, SwiftUI, Flutter, and Jetpack Compose) work best with, no surprise, reactive data flows. Reactive UIs, by definition, react to changes in their data models, so there is no need for a Presenter (which, by definition, controls the View). Any architecture that has data that can be observed, therefore, will have the least friction when using Reactive UI.</p><h3>MVVM</h3><p>The officially supported Android Architecture is <a href="https://developer.android.com/topic/libraries/architecture/viewmodel">MVVM</a> (Model-View-ViewModel); support comes in the form of documentation and library support. Google released this in 2017 as part of the Android Architecture Components (AAC) initiative.</p><p>Business logic in a ViewModel updates Fields created as LiveData (for apps not using Kotlin Coroutines) or StateFlow (for apps using Kotlin Coroutines). Apps using <a href="https://github.com/ReactiveX/RxJava">RxJava</a> typically keep all network and database calls and other threading in RxJava until they reach the ViewModel boundary where the RxJava streams are exposed as LiveData. For older Android XML layout based applications, Databinding is typically used to bind XML layout field attributes to the LiveData fields on the ViewModel. This typically looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/561/0*EBMAxD0WE23_tZBb" /></figure><p>Folks who adopted the MVVM architecture in 2017 can use this same architecture with Jetpack Compose. However, to optimize MVVM usage for Compose, the fields should be grouped into a state class.</p><p>Ironically, when AAC MVVM was announced, the Android community was already starting to adopt the <a href="http://hannesdorfmann.com/android/model-view-intent/">MVI</a> architecture a year earlier.</p><h3>MVI/Redux</h3><p>ReactJS was open sourced by Facebook in 2013 and has been the source of inspiration for most of today’s Reactive UI frameworks, and the techniques used in ReactJS also apply to Jetpack Compose. The Redux framework (<a href="https://en.wikipedia.org/wiki/Redux_(JavaScript_library)">open sourced in 2015</a>) is a very popular state management framework from the <a href="https://en.wikipedia.org/wiki/React_(JavaScript_library)">React</a>JS world. Redux isn’t usually used in the app world because it requires a global app data store but native apps have multiple screens that get created/destroyed, so you should only consider this for simpler apps.</p><p>MVI dates back to a library, inspired by Redux, called “<a href="http://hannesdorfmann.com/android/model-view-intent/">Mosby”, that Hannes Dorfman</a> created in 2016. MVI emits a continuous stream of screen state which Compose can consume directly; this means that the ViewModel is no longer needed in a pure Compose app (or you can consider the MVI state engine as the “viewmodel”). However, an Android Arch Components ViewModel is still useful for retaining state during a screen rotation and handling other Android lifecycle issues like saving state for app resurrection and providing a lifecycle for Kotlin coroutines.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9s2JQVHmtk8ED4xT" /></figure><p>State in this MVI architecture diagram is consumed directly by Reactive UIs like Compose. Because of the rigid definitions of events and state, state can also be played back (aka “time machine” or “time travel” support) for both MVI and Redux.</p><h3>BLoC</h3><p>BLoC is Google’s official framework for Flutter which stands for “block of logic”. It’s generally tied to a component/widget, but can also be wired together to create more complex business logic. It was created in 2019, but oddly was never ported over to Android by Google. BloCs are used like viewmodels in Dart.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/935/0*1_eSm-0E_Cr2BUsH" /></figure><h3>Elm/MVU</h3><p><a href="https://guide.elm-lang.org/architecture/">Elm</a> is a web frontend architecture that predates Redux and inspired the Redux (at least according to their documentation :-). It’s also very similar to MVI as you can see:</p><figure><img alt="MVU architecture from https://steemit.com/utopian-io/@tensor/using-the-elm-architecture-or-the-mvu-pattern-with-dartea-inside-of-dart-s-flutter-framework" src="https://cdn-images-1.medium.com/max/640/0*5T9W2itkLxFcwHuf.png" /><figcaption>From <a href="https://steemit.com/utopian-io/@tensor/using-the-elm-architecture-or-the-mvu-pattern-with-dartea-inside-of-dart-s-flutter-framework">https://steemit.com/utopian-io/@tensor/using-the-elm-architecture-or-the-mvu-pattern-with-dartea-inside-of-dart-s-flutter-framework</a></figcaption></figure><p>The main difference is that it provides a View which includes the rendering. In a Jetpack Compose context, an Elm architecture provides a Composable which is the view. This keeps the View rendering closer to the model for tighter coupling.</p><h3>Pros/Cons</h3><p>Every architecture has tradeoffs, so you’ll have to decide what works best for you.</p><p><strong>MVVM</strong> is the Google official standard, but does not rigorously enforce Unidirectional Data Flow (UDF). It will also be the most widely known by developers. The libraries are heavily tied to Android so business logic can’t be shared with Kotlin Multiplatform easily (the Moko MVVM library helps though). Least boilerplate (aka contract definitions with interfaces).</p><p><strong>Redux</strong> inherently enforces UDF but the data store is at the application level. “Time travel” debugging is supported.</p><p><strong>MVI</strong> inherently enforces UDF and business logic is easily tested. Data store “Time traval” debugging is supported. Data store is at the screen level.</p><p><strong>BLoC</strong> has been tested extensively by Flutter applications, but is not part of the Android Arch Components supported by Google. It also enforces UDF. “Time machine” support probably isn’t possible because of the private data stores. Logic maps to widget level while the frameworks usually map to screen/app level.</p><p><strong>MVU</strong> has tight coupling between the view and model which allows you to group them better and also has UDF enforcement. Tighter coupling doesn’t allow you to use the same business logic for different views, however.</p><h3>Usage in Jetpack Compose</h3><p>The short summary is that all these architectures can be used in Compose. If you’re still on MVC/MVP, migrate your code to use a UDF architecture for best results; during migration, you’ll find that if you want to keep MVC/MVP, the Presenter will just end up looking like MVVM with an observable state.</p><p>MVVM tends to encourage people to expose individual fields as observables because the DataBinding used in the old XML/Layout system needed widget properties exposed individually. People who have used MVVM prior to Jetpack Compose will tend to use separate fields in their viewmodels that are used for Compose as well.</p><p>In contrast, MVI and the other frameworks encourage screen state to be further subdivided into what is needed for each subsection of screen state. Compose widgets can take in a single view state class that is used for view state. MVI also tends to have more boilerplate in interface/class definitions to help enforce proper usage.</p><p>It’s also possible to build a layer over MVVM to create an MVVM+ framework which adds more guardrails to encourage using a single state class. We’ll cover existing MVI/MVVM+ frameworks and their tradeoffs in another blog.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=52502e49500f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Code Review Musings]]></title>
            <link>https://kenkyee.medium.com/code-review-musings-fc3428abf990?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/fc3428abf990</guid>
            <category><![CDATA[team-culture]]></category>
            <category><![CDATA[code-review]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[team]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Mon, 26 Jul 2021 12:29:09 GMT</pubDate>
            <atom:updated>2021-07-26T19:35:45.646Z</atom:updated>
            <content:encoded><![CDATA[<p>A coworker asked for a blog on how I approach code reviews for our company blog, but it didn’t feel like the right place to put it because a lot of this is personal philosophy vs. company philosophy. So here it is as a personal blog with some tips on how I efficiently process a high number (roughly 150-200/month) of PRs from 50+ Android developers and also some from backend developers.</p><h3>Why Do Code Reviews?</h3><p>A lot of developers look at code reviews as a chore. It’s somewhat understandable given how the typical Agile process does not reward you for doing code reviews, but for getting tickets done. But even your Agile process can be sped up by doing code reviews for others so that they’re more inclined to help review your PRs.</p><p>There are lots of other reasons to do code reviews, but these are mine:</p><ul><li>Helps your team increase team/company velocity — as part of the Platforms team, I consider all Android developers and the backend decoupling developers part of my team. The faster new features/fixes get to customers, the faster we can learn from them.</li><li>Lets you mentor others — this is especially helpful during language transitions from PHP to Java or Java to Kotlin. During that transition, everyone is learning a new language and may not know some of the nuances of the new language. If you have more experience with a certain technology/language/feature, it’s worth helping others learn by doing code reviews.</li><li>Lets you learn from others — I can’t count the number of times I’ve learned something from a code review. From new language nuances, to better usage of various SDKs, to better usage of internal frameworks, to new standards of coding. One thing that you can be assured of is that the code base changes, so doing code reviews is one of the best ways to learn about this.</li><li>Lets you set a good team culture — I tend to write code review comments as questions to foster discussion and learning. This provides an environment where people can think about what might be wrong or can encourage a discussion of pros/cons of a solution. Contrast this with a code review comment where someone says “this is wrong..do it this way”, which makes the receiver think less because: a) you provided the solution, and b) they’re less likely to ask questions because you’re telling them what to do.</li><li>Lets you see patterns for improvement — this is the same reason I help a lot of people out in a slack channels. You can sometimes see a pattern that can be addressed in a framework or template or lint rules so your team doesn’t keep making the same mistakes.</li></ul><p>How much time does it take? It normally doesn’t take me more than an hour a day; sometimes massive Pull Requests (PRs) take a half hour, but most do not. I think most developers should commit an hour a day to do if they agree w/ any of the reasons above.</p><h3>Code Reviews vs. Domain Reviews</h3><p>It should be noted that these are very different. Code reviews usually cover code issues or platform or algorithm or framework issues. Domain reviews still need to be done by team mates who work on the same team/features. Domain issues include nuances like what conditions some element of screen should be visible under, or how certain REST endpoints behave, or the proper flow of a screen…i.e., business logic. I do code reviews unless I’m on the team I’m doing reviews for, or if it’s a review for usage of a framework/SDK I’ve written.</p><h3>Doing Reviews More Efficiently</h3><p>It’s hard to do reviews efficiently without some workflow, so here’s what I do during my day.</p><p>The Github UI has an indication that a PR has been approved, but that doesn’t mean it has been reviewed by you. Everyone finds different things in a code review, so it always helps the coder and your team if you have time to do a review on an already approved PR. You can usually learn something new too :-)</p><p>Github’s web UI (and most other source control systems) has a way to filter open reviews by whether they have been reviewed by you, or whether a review is requested from you, etc. I typically refresh a screen with the “Not reviewed by you” dropdown during a build and take care of the simple/small ones immediately. Not everything is simple, so I’ll look at the bigger ones when I have a bigger block of free time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/608/1*zZVjtvik-0rNUdq_7z_tSQ.png" /><figcaption>The last two filters are the most useful</figcaption></figure><p>Several times a day and before I end the day, I also look at the “Awaiting review from you” category.</p><p>You can make incremental reviews (reviews of the same PR) less work by marking files that you’ve looked at as “Viewed”. Each file in the Github PR diff has a checkbox you can click to tag the file so you don’t have to look at it every time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/294/1*LY3Bu1cFqlDYXL5RNyXXPw.png" /><figcaption>Use this checkbox to make incremental reviews quicker</figcaption></figure><p>Github has a lot of email notifications. Once you review a PR, you get a notification for every commit or comment. Set up a mail filter to dump all these notifications into a folder and look through this folder a few times a day; sometimes, you’ll find the discussion in a PR interesting and end up joining the discussion. I usually set up different folders for Android, iOS, and backend reviews.</p><p>Lastly, when you add comments that aren’t questions, be sure to add some indication for whether it’s an optional request or required change. At an agency I worked at, we used notations like “#nth” (nice to have) and “#req” (required) that are prefixed to the comment.</p><h3>How to Help Code Reviewers</h3><p>I’ve noticed that some code reviews are a lot easier if people put up PRs that explain changes better.</p><p>The minimal amount of info we require in a PR is approvers, a JIRA ticket, and a short description. There is a line in the PR template that says “remove this” that should be removed as well (you wouldn’t believe how often the template is not filled in properly by not removing this). A longer description describing why something fixes a bug helps more than “fixes crash in xxxx”.</p><p>For screen changes, putting up before/after screenshots help immensely because reviewers don’t have to load up the branch build to see if something looks right. But what helps most is having coders do a review of their own PR. Adding their own comments to point out where bug fixes were actually done or explaining something that might look a little confusing to others who are not domain reviews provides context for the reviewer. The best part of doing a self-review of your PR is you can fix stuff that you should not have committed before a reviewer has to point it out.</p><p>Most companies have a code reviews slack channel. If PRs are posted there, that helps let people know that you want a review. This is especially helpful if a PR has been marked “WIP” or “Draft” for a while.</p><p>When you put up a review, be sure to request a review in the Github UI from anyone you think your code might affect. Github has a CODEOWNERS file that automatically requests reviews from people who “own” the files your PR touches, but if there are other folks who your code might affect or who might be interested in your code (think of folks on other teams as well…e.g., if you’re adding something on Android, but the iOS folks might be interested in doing the same thing, it’s ok to request a review from someone on a different team to let them know about it).</p><p>If you have a PR up, and folks have commented on it already, you may need to push a few commits up but the PR might not be ready for another review. Next to a reviewer’s name on the upper right of the PR screen, there is a “refresh” button (the circular arrows). Clicking on this will request another review from that reviewer via an email notification. This is a lot less disruptive than pinging the reviewer directly because reviewers can batch review the ones they’ve reviewed before.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/136/1*hN4WwDTUFhBpGyTok0HVNg.png" /><figcaption>Recycle button requests another review from the reviewer</figcaption></figure><p>Hope these efficiency tips help you. If you have any more of your own, let me know in the comments so I can be more efficient :-)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fc3428abf990" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Predictions/Goals: Past and Future]]></title>
            <link>https://kenkyee.medium.com/predictions-goals-past-and-future-1633fbb92a68?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/1633fbb92a68</guid>
            <category><![CDATA[company]]></category>
            <category><![CDATA[predictions]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Thu, 22 Jul 2021 01:21:29 GMT</pubDate>
            <atom:updated>2021-07-25T12:27:16.311Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/137/0*VBfgQvCninlS8l_v" /><figcaption>From <a href="http://www.marvunapp.com/Appendix4/probabilityengine.htm">http://www.marvunapp.com/Appendix4/probabilityengine.htm</a></figcaption></figure><p>With my 2nd year anniversary at my current company, I thought it’d be a good time to have a bit of a retrospective/introspective/predictive on the <a href="https://kenkyee.medium.com/introspective-on-being-an-architect-tech-lead-team-lead-9bafef324d7d">Navigation responsibilities of an architect</a>. Futures are inherently not 100% probability of success, but what fun is a New Year’s resolution w/o outing it so you can see if you make it? :-)</p><h3>Retrospective</h3><p>First we’ll go over the state of things when I started and things I was able to change. After spending the first month getting up to speed and then getting embedded in an AR/VR team, various macro issues became visible throughout the year:</p><ul><li><strong>Pull Requests took too long to merge</strong><br>While getting up to speed by doing PR reviews and trying to update the app’s libraries, I noticed it took days to get eyes on a review because people hadn’t gotten into the habit of going through reviews daily; there was a code review channel you could ask for a review in, but folks usually avoided reviewing large PRs.<br>Team velocity is directly proportional to PR velocity, so I continued reviewing just about every PR that was put up for the Android team. This wasn’t noticed until we added GitPrime a year later. It was gamified a bit by my manager so it’s not me dominating the leaderboard now but still can be improved.</li><li><strong>Bad integration w/ Firebase</strong><br>The app was still on Crashlytics. Libraries were outdated enough that this could not be done easily. Frequent library updates require smaller deltas, so I initially tried a monthly cadence (with Google’s monthly release cadence) but that ended up at a quarterly cadence because even monthly updates took a lot of time.<br>Firebase is more than Crashlytics though, which brings up the next item…</li><li><strong>No network performance monitoring</strong><br>There was some performance observability, but it was mostly comprised of attempts to match measurements on web (measurements were even called “page load time” on app). <br>There was no observability of network performance. I was able to integrate Firebase Performance (I also integrated this with Kayak’s app and was part of the EAP/beta) to prove that this was useful. After initial integration, we found an API that the app still called but always returned 400 errors. The server side folks also had not prioritized observability at the time so this was never noticed. There were also a set of APIs that were much slower than others that we flagged for the backend folks using Firebase Perf measurements.<br>Later on, I built out a Performance Framework that lets us selectively log to our backend (which uses InfluxDB) and Firebase Performance. DataDog and Perfetto were later easily added to this framework as plugins.</li><li><strong>Builds took too long</strong><br>Builds were taking too long both locally and in CI. In CI, I solved issues like multiple jobs running duplicate unit testing and a Sonarqube plugin that had a bug that caused other flavors to be built. These issues reduced CI pipeline time from 45min to 18min.<br>Locally, builds were taking too long primarily because the laptops didn’t have enough memory. After optimizing our builds down, I still found that the working set was 24GB of memory on a 16GB machine; iOS folks didn’t have this issue because they were CPU constrained. It took a long time to convince management, but this brought full build times down from 20min to 10min.<br>I also found that our antivirus software had a huge memory leak of 500MB/day which would also cause swapping. I had to pester the IT folks who then made Crowdstrike fix a 3yr old memory leak.</li><li><strong>App was too flaky</strong><br>There were too many “stuck” screens when there were network issues. Screens would be stuck at loading screens or white screens or display products with prices of $0 (aka “free”). The free prices were because of handling random null fields from server APIs by using the Kotlin construct:<br> displayedPrice = serverPrice ?: 0.0<br>Fixing this required convincing folks via PR comments to use Float.NaN as a default instead of 0.0 as a default so the UI could handle these cases.<br>A teammate took care of error handling by building a framework that handled the LCE pattern. The LCE pattern was drilled into me from my time at RaizLabs but sadly not at many other places I know of.</li><li><strong>App was too slow</strong><br>This was caused by multiple issues including the aforementioned slow APIs. We were also downloading full sized images. From previous work at KAYAK, I knew images could be reduced to half size w/o users noticing. This helped reduce our network bandwidth immensely.<br>Another source of slowness was our app screens not preloading data before the users hit them. I built a PreloadManager framework that helped developers preload data on app startup so it was available by the time users used the screen.</li><li><strong>Broken analytics</strong><br>We have some quirky analytics requirements which include a tracking ID for data displayed on a screen; the tracking ID is needed for ad attribution. However, this requirement was not enforced. I added a guard rail to crash in a local dev environment if that tracking ID was missing.<br>We also have a quirky requirement that’s the equivalent of requiring “http referrer” for all screens on the app. This was broken in various ways including an interstitial screen that was added by a feature team. I modified our analytics SDK to support interstitials; the SDK was also modified to crash developers who didn’t handle the “http referrer” requirement correctly.</li><li><strong>QA wasn’t efficiently getting App Builds<br></strong>The QA folks were downloading APKs manually off Jenkins. I added Firebase App Distribution to our CI process for all our whitelabels and other apps like a driver app and UI demo app (11 projects because we have dev/prod environments). All PRs automatically pushed builds for QA to use. After QA was satisfied with this, I also helped set this up for iOS builds as well so they could use the same app to search for and download builds to test. QA was much happier to say the least :-)</li><li><strong>Perfecto vs. Firebase Test Lab</strong><br>When I started, the Quality Eng team had already started integrating Perfecto. I thought this was a bit odd of a choice because it wasn’t a very mainstream solution. Firebase Test Lab, on the other hand was. I knew several large companies (Amex and Citibank) had used it and there was great tooling already built to shard tests on it by LinkedIn. I integrated FTL into our Buildkite jobs to show how simple it was to set up and that it had all the features of Perfecto (including video recordings of failures) while also costing much less; it’s now part of our PR smoke tests.</li><li><strong>Decoupling needed a smoother road and guides</strong><br>This has taken up a good part of my second year at this company. Part of our move to the cloud involved decoupling APIs from our PHP monolith to smaller Java Spring Boot microservices. I was involved early on and reviewed/improved some of the training material, then helped review any PRs when folks were starting to decouple (most were PHP devs learning Java so they didn’t have experienced Java developers on their team). I also improved DataDog support and added Swagger support to the autogenerated templates folks used because the early adopters were having a hard time using/integrating these. <br>Our official standard for microservices is Maven and Java, but I also created a microservice with Kotlin and Gradle to show how easy and less verbose they were. The microservice was well documented and handled categorization of errors to provide a good example for others to follow.</li></ul><p>A good deal of this was not part of my directly assigned duties, but luckily I had a supportive team and management that trusted that I was doing what’s best for the company while I also took care of my assigned duties. At smaller companies, it’s easy to provide course changes no matter who you are or when you are; a few people in a room can move the entire company’s direction in a few weeks. At larger ones, it’s more difficult unless you’re upper management, but you can still help the giant ship go in the right direction with well-timed nudges early enough (i.e. don’t wait until something bad has set in deeply so it’s too hard to move).</p><p>But you may say, hindsight is easy, what about the future?</p><h3>Futures</h3><p>Before describing what I think are possible futures, it would help to explain what helps me make predictions. By reviewing most PRs, I can see what guardrails are needed and what pains are felt by developers. By following over 230+ Slack channels and doing horizontal mentoring, I can get a sense of what issues need addressing through the company. By monitoring other Kotlin/Android communities (Slack and blogs and conferences), I can get a sense of what will be upcoming for technologies. By participating in Google UX focus groups and beta programs, I can help influence some of the rough edges in products and see what’s coming earlier. In short, plugging in and being connected provides enough data points to predict relatively accurately a year into the future.</p><p>These are the main top of mind topics for me in the upcoming year with probabilities of getting them done:</p><ul><li><strong>Jetpack Compose Migration (80%)</strong><br>This has to be the main consideration for any Android app in the next year. Jetpack Compose is an entirely new mindset that has the huge potential to improve development times for Android developers. However, what’s simple to integrate and use at smaller companies is a lot harder when you have a company with 50 Android developers, 13 teams, and a huge codebase written with the VIPER architecture. This is a large undertaking because you have to consider migration, guardrails for developers, new testing paradigms, and better frameworks.<br>The first major issue is the architecture. Our current application is done using VIPER (took 2 years to migrate from MVP). Jetpack Compose is a reactive architecture, which has no “P — Presenter”. It would have been much simpler if we had used an MVVM architecture which is compatible with a reactive UI. I’ve done a presentation/proposal to create an MVI/Redux framework behind VIPER so that when a feature is migrated to Compose, it should be a simple removal of the VIPER architecture.<br>There is a lot more than just an architecture change though. With large companies, you have to build guardrails to help developers do the right thing. We have this complex internal analytics system that tracks the equivalent of “http referrer” on native app, so safeties have to be put in place to make sure that’s handled properly. In addition, we have a performance framework that needs to be hooked into all screens to measure load/wait times for users. We also have an LCE (Loading/Content/Error) pattern that should be enforced so developers account for all these cases on their features. One of the biggest issues with the current VIPER framework is there were no lint rules to enforce correct component calling and that’s a mistake we should not make again.<br>Besides all that, we are starting to do UI integration testing and that is very different with Compose. This also has to include accessibility testing. Screenshot testing seems like a natural fit for this.<br>Jetpack Compose is also a natural fit for server driven UI which we’ll be attempting with an internal content management system (CMS). There’s also the question of whether Flutter would work better for this use case.<br>As you can see, there are a lot of moving parts to getting us onto Jetpack Compose.</li><li><strong>Speed Improvements(80%)</strong><br>Despite having made all the speed improvements we did last year, there is space for a lot of other improvements.<br>For developer builds, we should look at the new M1x MacBooks to see if we can justify upgrading to them again (and hopefully in less than the year+ it took us last time). If they’re not fast enough, remote builds are another option we can use. Our current 32GB Macbooks thermal throttle way too much, even though they’re a lot better then the old 16GB Macbooks.<br>For speeding up the build in general, there are still opportunities to build only modules that users need to build. There are a few blog articles and techniques I’ve seen to minimize the modules that need to be built. Our CI can be parallelized more because we have a 40min job that runs all our tests. There are tools that let you find out which modules have changed so we don’t need to run all the tests and this can be applied to our UI testing as well.<br>There are other things that might help to improve build speed but they require code changes. e.g. using <a href="https://github.com/square/anvil">Square’s Anvil</a> to remove kapt from a module.</li><li><strong>Backend improvements and app API decoupling (70%)</strong><br>We chose a “safe” standard for the API decoupling effort (Spring Boot with SpringMVC and Java) but as we scale out to thousands of microservices, cost becomes a very real issue. For high traffic, reactive frameworks like Spring WebFlux don’t require as many containers for the same amount of traffic as SpringMVC. The JVM and Linux images take up a large amount of space and memory. GraalVM AOT lets JVM applications be compiled down into executables that aren’t much bigger than Golang executables and they start up a lot faster and use less memory, so scaling is much more efficient; large companies like Alibaba already use GraalVM. For a microframework, Quarkus looks extremely promising in not only the support for GraalVM, but the extremely fast development cycles with TestContainers.<br>Our native app feature toggles are in serious need of a rebuild on the backend as well as client side. The server side feature toggles are in the PHP monolith and should be replaced. This can be replaced with Firebase Remote Config with an in-house admin UI layer that is friendly for everyone to use. This was a project I was hoping to get to last year but didn’t have time to. <br>Logging from native app is super inefficient (we send enough to DDOS our Kibana servers because we filter server-side). The ability to send down configuration strings for logging from Remote Config will give us a lot more configurability and controlled observability when we have issues in a particular section of code as well as reducing traffic we send to our poor servers.<br>Because the PHP monolith is going away, we have to decouple our remaining native app endpoints. We have a startup endpoint that is hit on app startup that could be made faster by decoupling it (assuming we can be independent of PHP sessions). Our logging endpoints are also PHP and could be switched to Golang or Quarkus; I started work on a Golang version but shelved it because our infrastructure was not ready to expose public external decoupled endpoints at the time.</li><li><strong>Automation and AI (20%)</strong><br>Our AppDirectory and Code Quality metrics can continue to be used for automation (more on our Code Quality metrics initiative should be in a forthcoming company blog). Now that we have ownership metadata in our repositories, we can use that to automate user feedback from the app stores via simple keyword matching or via ML classification.<br>For more longshot initiatives, the natural language tools (LaMDA) that Google should be adding to GCP soon can give us the ability to add a smart shopping assistant to the app as well let us add a first-level virtual call center assistant to reduce the load on our call center personnel at high traffic times.</li><li><strong>New Kotlin features (50%)</strong><br>Kotlin MultiPlatform may finally be ready for usage. iOS developers might not hate us for sharing a library with them because the new iOS mem model and XCFramework support this Fall will make it an acceptable experience instead of the multithreading/freezing fun it is now. Our internal Scribe analytics library is a clear candidate for this because it has no UI and the upcoming Avro schema for the analytics events has nice Gradle support.<br>The improved Kotlin IR backend also means that Kotlin/JS might be stable enough for usage. This has much better support for debugging Kotlin/JS code and will let us replace our internal React/JS UIs with Kotlin/JS without worrying about a lot of Kotlin/JS API changes.<br>Kotlin coroutines seem to be getting more widely used in Google libraries we depend on. Up to this point, they’ve offered interop w/ the RxJava we use but it’s obvious that coroutines are the future. In a large codebase, RxJava has more features than coroutines, so it may not be needed, but coroutines are definitely more understandable by people so we should evaluate them. For all new projects I’ve worked on, I’ve used coroutines and they are easier to use in most cases, and also less verbose, but they also come with new footguns we have to document/mitigate.</li><li><strong>New platform features (20%)</strong><br>This year appears to be a strong push for Android Foldable devices (though I suspect they won’t take off until Apple sells one). Google appears to have provided enough ecosystem improvements to make them easy to develop for so it’s not a lot of work to support large screen devices like Foldables and Chromebooks. The rumored foldable Pixel should also help the ecosystem if cost is close enough to normal phones instead of the 2x cost for foldables currently.<br>Android Widgets will also hopefully get some love from our UX folks now that iOS has them. There are compelling use cases like a delivery tracking widget or a hot sales widget.</li></ul><p>There are other organization level changes that can improve team speeds, but the above list only covers things I can directly influence.</p><p>The next retrospective should be interesting. Has stating this publicly changed the future already? How far do goals/predictions change even 6–9 months out and how much of this will be done in a year? We’ll find out… ;-)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1633fbb92a68" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring Boot Permissions/Routing/Packaging Fun with React.js and Okta]]></title>
            <link>https://kenkyee.medium.com/spring-boot-permissions-routing-packaging-fun-with-react-js-and-okta-cbe19227c1be?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/cbe19227c1be</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[okta]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Sat, 10 Jul 2021 18:18:26 GMT</pubDate>
            <atom:updated>2021-07-10T18:22:30.792Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>This common setup isn’t as well documented as I expected given how prolifically the Okta folks blog, so I’m going to cover the issues I encountered recently when setting up a Spring Boot microservice API protected with Okta authentication and used by a React.js web app (sometimes referred to as an SPA or Single Page App) that is also served by the same microservice. I’ll also cover some of the issues I hit getting it packaged for use in production.</p><h3>JWT vs. OIDC</h3><p>First of all, it’s important to understand how you’re configuring Spring Boot with Okta authentication. You can either set it up for OIDC (basically OAuth2) authentication or JWT token (signed token with metadata) authentication.</p><p>OIDC is not used for Single Page Application web clients like React.js. The authentication forms and flows stay within Spring Boot with OIDC and session management is also done in Spring Boot. This flow requires an Okta ClientId/Secret keypair to be kept on the server so it can generate access tokens.</p><p>JWT authentication on the other hand is done with tokens that are validated by Okta servers. The authentication flow is done mostly on the web client (we’ll get to the “mostly” part when we configure Spring permissions). Both the server and the client only require the Issuing URL (used to validate tokens) and the ClientId. The React.js Okta library handles connection to the Okta server and refreshing the JWT token. The server just validates tokens it gets from the React.js client with Okta. All JWT scopes (e.g., email and groups) are managed on the Okta server.</p><h3>CSRF</h3><p>CSRF should be disabled for microservices that support only API calls and SPA. It’s only used for Form authentication based web sites. This is why CSRF tokens are needed to prevent malicious attacks from form submission:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/550/0*Z_WRyX-FIL-Y7IX1.png" /><figcaption>CSRF diagram from <a href="https://phppot.com/php/cross-site-request-forgery-anti-csrf-protection-in-php/">https://phppot.com/php/cross-site-request-forgery-anti-csrf-protection-in-php/</a></figcaption></figure><h3><strong>CORS</strong></h3><p>During development of any React.js app, you’ll have to handle Cross Origin Resource Sharing. This means that your Javascript code is trying to contact a hostname/URL that isn’t the same URL as where your web app was loaded by the web browser. React.js apps typically are at <a href="http://localhost:3000">http://localhost:3000</a> during development. At the network level, this is what actually happens so you can recognize it in Chrome devtools:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Zi8zkH6NU-tpjMJk.png" /><figcaption>CORS Network Sequence from Wikipedia</figcaption></figure><h3>Authorization Calls</h3><p>Now that we have an understanding of JWT and CORS, we can see what the network traffic looks like for the login and logout process. We have to understand this a bit before we configure Spring Boot to handle everything.</p><p>As you can see in the diagram below, Okta login will redirect to /login/callback and include the JWT token. The logout will redirect to “/”. It’s important to note that these URLs go to your <strong>server</strong>, not back to the React.js client, so the server has to forward back to the React.js client in the browser.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0JBfCWr3xWpsgTy9HA7zbQ.png" /><figcaption>Okta JWT Login/Logout</figcaption></figure><h3>Okta Configuration</h3><p>To support debugging of the React.js client, you’ll have to go to Security/API/TrustedOrigins in your Okta Admin Dashboard and add these values to the allowed CORS endpoints:</p><p><a href="http://localhost:3000">http://localhost:3000</a><br><a href="http://localhost:8080">http://localhost:8080</a></p><p>If you want to use only localhost:8080, you can also edit your package.json and add a proxy to get past CORS issues w/ React.js which Yarn starts on port 3000 (this is typically not worth it):</p><pre>&quot;proxy&quot;: &quot;http://localhost:8080&quot;,</pre><p><strong>Login Redirect URLs</strong></p><p>In the Okta Admin Dashboard, you’ll also have to configure your valid login redirect URLs as:</p><p><a href="https://kube-appdirectory.service.intrabo1.consul.csnzoo.com/login/callback">https://www.yourdomain.com/login/callback</a><br><a href="http://localhost:3000/login/callback">http://localhost:3000/login/callback</a><br><a href="http://localhost:8080/login/callback">http://localhost:8080/login/callback</a></p><p><strong>Logout Redirect URLs</strong></p><p>And finally, in the Okta Admin Dashboard, you need to configure your valid logout redirect URLs. All these are required to ensure your site is secure and the JWT authentication happens properly.</p><p><a href="https://www.yourdomain.com">https://www.yourdomain.com</a><br><a href="http://localhost:3000">http://localhost:3000</a><br><a href="http://localhost:8080">http://localhost:8080</a></p><h3>Spring Boot Permissions</h3><p>Spring Boot permission configuration can be categorized into setup for CSRF, CORS (during local development), configuration for the React.js client, and permissions for your APIs that need JWT tokens.</p><p>CORS configuration and React.js resource handling is done via a class that provides a WebMvcConfigurer Bean:</p><pre>@Configuration<br>class CorsConfig<em>(</em>val environment: Environment<em>) {<br><br>    </em>val isLocalEnv: Boolean<br>        get<em>() </em>= environment.<em>activeProfiles</em>.<em>contains(</em>&quot;local&quot;<em>)<br><br>    </em>@Bean<br>    fun corsConfigurer<em>()</em>: WebMvcConfigurer <em>{<br>        </em>return object : WebMvcConfigurer <em>{<br>            </em>override fun addCorsMappings<em>(</em>registry: CorsRegistry<em>) {<br>                </em>if <em>(</em>isLocalEnv<em>) {<br>                    </em>// this disables CORS so we can debug the React client easily<br>                    registry.addMapping<em>(</em>&quot;/**&quot;<em>)<br>                        </em>.allowedHeaders<em>(</em>&quot;*&quot;<em>)<br>                        </em>.allowedOriginPatterns<em>(</em>&quot;*&quot;<em>)<br>                        </em>.allowedMethods<em>(</em>&quot;POST&quot;, &quot;PUT&quot;, &quot;PATCH&quot;, &quot;DELETE&quot;, &quot;HEAD&quot;, &quot;OPTIONS&quot;, &quot;DELETE&quot;, &quot;GET&quot;<em>)<br>                        </em>.allowCredentials<em>(</em>true<em>)<br>                }<br>            }<br><br>            </em>override fun addViewControllers<em>(</em>registry: ViewControllerRegistry<em>) {<br>                </em>// this routes paths to the React.js client routes;<br>                // excludes our service&#39;s read-only APIs, health endpoints, and Wwagger<br>                registry.addViewController<em>(</em>&quot;/web/**&quot;<em>)<br>                    </em>.setViewName<em>(</em>&quot;forward:/&quot;<em>)<br>                </em>registry.addViewController<em>(</em>&quot;{spring:^((?!/v?/|/health/|/swagger-ui/).)*$}&quot;<em>)<br>                    </em>.setViewName<em>(</em>&quot;forward:/&quot;<em>)<br>            }<br><br>            </em>override fun addResourceHandlers<em>(</em>registry: ResourceHandlerRegistry<em>) {<br>                </em>// this is used to handle the /login/callback from Okta from React.js<br>                registry.addResourceHandler<em>(</em>&quot;/login/**&quot;<em>)<br>                    </em>.addResourceLocations<em>(</em>&quot;classpath:/static/&quot;<em>)<br>                    </em>.resourceChain<em>(</em>true<em>)<br>                    </em>.addResolver<em>(</em>object : PathResourceResolver<em>() {<br>                        </em>override fun getResource<em>(</em>resourcePath: String, location: Resource<em>)</em>: Resource <em>{<br>                            </em>val requestedResource: Resource = location.createRelative<em>(</em>resourcePath<em>)<br>                            </em>return if <em>(</em>requestedResource.exists<em>() </em>&amp;&amp;<br>                                requestedResource.<em>isReadable<br>                            ) </em>requestedResource else ClassPathResource<em>(<br>                                </em>&quot;/static/index.html&quot;<br>                            <em>)<br>                        }<br>                    })<br>            }<br>        }<br>    }<br>}</em></pre><p>The WebMvcConfigurer’s addCorsMappings() only disables CORS if the currently active Spring profile is “local” which indicates it’s the local dev environment.</p><p>The React.js client’s routes live in the /web path and the overridden addViewControllers() adds all the special routing for it so that the browser gets routed properly by React.</p><p>Finally, addResourceHandlers() adds handling for any images in the /static folder and redirects for Okta’s /login callback.</p><p>A class that subclasses WebSecurityConfigurerAdapter is used to configure CRSF and API endpoint permissions:</p><pre>@Configuration<br>class SecurityConfiguration<em>(</em>val environment: Environment<em>) </em>: WebSecurityConfigurerAdapter<em>() {<br><br>    </em>val isLocalEnv: Boolean<br>        get<em>() </em>= environment.<em>activeProfiles</em>.<em>contains(</em>&quot;local&quot;<em>)<br><br>    </em>override fun configure<em>(</em>http: HttpSecurity<em>) {<br>        </em>http.sessionManagement<em>()<br>            </em>.sessionCreationPolicy<em>(</em>SessionCreationPolicy.<em>STATELESS) </em>// web UI is SPA<br><br>        // CSRF tokens not needed for SPA<br>        http.csrf<em>()</em>.disable<em>()<br><br>        </em>http.authorizeRequests<em>()<br>            </em>.requestMatchers<em>(</em>EndpointRequest.to<em>(</em>HealthEndpoint::class.<em>java))</em>.permitAll<em>()<br>            </em>.antMatchers<em>(</em>&quot;/**/*.{js,html,css}&quot;, &quot;/&quot;, &quot;/static/**&quot;<em>)</em>.permitAll<em>()<br>            </em>.antMatchers<em>(</em>&quot;/web/**&quot;<em>)</em>.permitAll<em>()<br>            </em>.antMatchers<em>(</em>&quot;/built/**&quot;, &quot;/images/**&quot;, &quot;main.css&quot;, &quot;/favicon*&quot;, &quot;/site.webmanifest&quot;<em>)</em>.permitAll<em>()<br>            </em>.antMatchers<em>(</em>&quot;/swagger-ui/**&quot;, &quot;/swagger-ui.html&quot;, &quot;/v3/api-docs/**&quot;<em>)</em>.permitAll<em>()<br>            </em>.antMatchers<em>(</em>&quot;/actuator&quot;, &quot;/actuator/**&quot;<em>)</em>.permitAll<em>()<br>            </em>.antMatchers<em>(</em>&quot;/v1/query/**&quot;, &quot;/v1/ui/**&quot;<em>)</em>.permitAll<em>()<br>            </em>.antMatchers<em>(</em>&quot;/login/**&quot;, &quot;/logout&quot;<em>)</em>.permitAll<em>()<br>            </em>.anyRequest<em>()</em>.authenticated<em>()<br>            </em>.and<em>()<br>            </em>.oauth2ResourceServer<em>()</em>.jwt<em>()<br><br>        </em>// Send a 401 message to the browser (w/o this, you&#39;ll see a blank page)<br>        Okta.configureResourceServer401ResponseBody<em>(</em>http<em>)<br>    }<br><br>    </em>@Throws<em>(</em>Exception::class<em>)<br>    </em>override fun configure<em>(</em>web: WebSecurity<em>) {<br>        </em>web.ignoring<em>()</em>.antMatchers<em>(</em>HttpMethod.<em>OPTIONS)<br>    }<br>}</em></pre><p>First, since we don’t support server-side web pages, we can turn off sessions so they don’t waste memory. After that, CSRF is disabled because we don’t need it.</p><p>For URL permissions, we allow all the static resources needed by the React.js client. We also allow unauthenticated access to the Swagger UI and heath endpoints. Next we allow access to APIs that the React.js client uses as well as login/logout redirects. Anything not mentioned in this configuration is then configured to use JWT authentication by default.</p><p>The http <em>OPTIONS</em> method is also configured as allowed, because web browsers use this to check whether CORS is configured properly for the React.js call to the APIs.</p><h3>MockMvc JWT Testing</h3><p>Doing testing of authenticated APIs was also not documented that well; there is an Okta blog that describes how to do OIDC testing with Spring’s MockMvc that almost gives you enough hints to get it working.</p><p>Spring’s website doesn’t document this well either, so I had to get some help from folks on Gitter.im’s Spring Security forum (big thanks to <a href="https://medium.com/u/c5920b02a6a7">Nicolas Fränkel</a> pointing me in the right direction).</p><p>The core code is the following that sets up a valid security context with a JWT token:</p><pre>private fun <strong>authenticationToken</strong>(jwtToken: Jwt): <em>AbstractAuthenticationToken </em>{<br>    return JwtAuthenticationConverter().apply {<br>        val claim = jwtToken.claims[&quot;sub&quot;] as String<br>        setPrincipalClaimName(claim)<br>    }.convert(jwtToken)!!<br>}<br>private fun <strong>setupJwtMvcContext</strong>(email: String = &quot;kyee@somewhere.com&quot;) {<br>    val jwt = Jwt.<strong>withTokenValue</strong>(ID_TOKEN)<br>        .header(&quot;alg&quot;, &quot;none&quot;)<br>        .claim(&quot;sub&quot;, email)<br>        .build()<br>    SecurityContextHolder.getContext().<em>authentication </em>= <strong>authenticationToken</strong>(jwt)<br>    val authInjector = SecurityContextHolderAwareRequestFilter()<br>    authInjector.afterPropertiesSet()<br>    mvc = MockMvcBuilders.webAppContextSetup(this.context).build()<br>}</pre><pre>companion object {<br>    // just a valid dummy JWT token<br>    // see <a href="https://developer.okta.com/blog/2019/04/15/testing-spring-security-oauth-with-junit">https://developer.okta.com/blog/2019/04/15/testing-spring-security-oauth-with-junit</a><br>    private const val ID_TOKEN = &quot;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9&quot; +<br>        &quot;.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsIm&quot; +<br>        &quot;p0aSI6ImQzNWRmMTRkLTA5ZjYtNDhmZi04YTkzLTdjNmYwMzM5MzE1OSIsImlhdCI6MTU0M&quot; +<br>        &quot;Tk3MTU4MywiZXhwIjoxNTQxOTc1MTgzfQ.QaQOarmV8xEUYV7yvWzX3cUE_4W1luMcWCwpr&quot; +<br>        &quot;oqqUrg&quot;<br>}</pre><p>You can then call setupJwtMvcContext<em>() </em>at the beginning of a test to load the JWT info into the security context:</p><pre>@Throws<em>(</em>Exception::class<em>)<br></em>@Test<br>fun testAddAdminInvalid<em>() {<br>    </em><strong>setupJwtMvcContext</strong><em>()<br>    </em>val addRequest = AdminUser<em>(<br>        </em>email = &quot;invalidemail&quot;,<br>        name = &quot;Test User&quot;<br>    <em>)<br><br>    </em>val postBody = addRequest.<em>asJsonString()<br>    </em>mvc.perform<em>(<br>        </em>MockMvcRequestBuilders.post<em>(</em>&quot;/v1/admin&quot;<em>)<br>            </em>.content<em>(</em>postBody<em>)<br>            </em>.contentType<em>(</em>MediaType.<em>APPLICATION_JSON)<br>            </em>.accept<em>(</em>MediaType.<em>APPLICATION_JSON)<br>    )<br>        </em>.andExpect<em>(</em>MockMvcResultMatchers.status<em>()</em>.<em>isBadRequest)<br>}</em></pre><p>You can then retrieve the email address from the JWT token by doing this in your controller:</p><pre>@PostMapping<em>(</em>&quot;admin&quot;<em>)<br></em>@PreAuthorize<em>(</em>&quot;hasAuthority(&#39;SCOPE_email&#39;)&quot;<em>)<br></em>fun addRepo<em>(<br>    </em>@RequestBody addAdminRequest: AddAdmin,<br>    @<strong>AuthenticationPrincipal</strong> <strong>jwt</strong>: Jwt<br><em>) {<br>    </em>val email = validateEmail<em>(</em>jwt<em>)<br>    </em>adminService.addAdmin<em>(</em>addAdminRequest<em>)<br>    </em>logger.info<em>(</em>&quot;<em>${</em>addRepoRequest.repoName<em>}</em> added by $email&quot;<em>)<br>}</em></pre><h3>Okta Javascript Library</h3><p>Hopefully, Okta’s authentication javascript library is stable now, but be aware that the API can change a lot between versions. The blogs also can be using older versions of the API. You can find the latest here:</p><p><a href="https://github.com/okta/okta-react">okta/okta-react</a></p><p>My project was done using the 5.0.x version of the API and it has gone to version 6.0 within the past 6 months, so that gives you an idea of how rapidly it changes.</p><p>Using it is fairly simple. Your Router has to be wrapped for it to handle authentication checking routes and you have to add a route to handle the login callback. Routes that require an authenticated user has to use the SecureRoute tag:</p><pre>const oktaAuthConfig = new OktaAuth<em>(</em><strong><em>config</em></strong>.oidc<em>)</em>;<br><br>const App = <em>() </em>=&gt; <em>{<br>    </em>const restoreOriginalUri = async <em>(</em>_oktaAuth, originalUri<em>) </em>=&gt; <em>{<br>        </em><strong><em>window</em></strong>.location.href = toRelativeUrl<em>(</em>originalUri, <strong><em>window</em></strong>.location.origin<em>)<br>    }</em>;<br><br>    return <em>(<br>        &lt;</em>Router<em>&gt;<br>            &lt;</em>Security oktaAuth=<em>{</em>oktaAuthConfig<em>} </em>restoreOriginalUri=<em>{</em>restoreOriginalUri<em>}&gt;<br>                &lt;</em>Container text style=<em>{{</em>marginTop: &#39;none&#39;<em>}}&gt;<br>...<br>&lt;</em>Switch<em>&gt;<br>    &lt;</em>Route path=<em>{</em><strong><em>config</em></strong>.oidc.callbackPath<em>} </em>component=<em>{</em><strong><em>LoginCallback</em></strong><em>}/&gt;<br>    &lt;</em>Route<br>        path=&#39;/&#39;<br>        exact<br>        render=<em>{</em>props =&gt;<br>            <em>&lt;</em>RepoList <em>{</em>...props<em>} </em>mineOnly=<em>{</em>false<em>}/&gt;}<br>    /&gt;<br>    &lt;</em>SecureRoute<br>        path=&#39;/web/edit/:id&#39;<br>        render=<em>{</em>props =&gt;<br>            <em>&lt;</em>Edit <em>{</em>...props<em>} /&gt;}<br>    /&gt;<br>&lt;/</em>Switch<em>&gt;<br>/&gt;</em></pre><p>You can then have a navigation bar component wrapped with withOktaAuth that checks for a logged in user and put up Login/Logout buttons as appropriate:</p><pre>export default <strong><em>withOktaAuth</em></strong><em>(</em>class NavigationBar extends Component <em>{<br><br>    </em>constructor<em>(</em>props<em>) {<br>        </em>super<em>(</em>props<em>)</em>;<br>        this.state = <em>{</em>isOpen: false<em>}</em>;<br>        this.toggle = this.toggle.bind<em>(</em>this<em>)</em>;<br>        this.login = this.login.bind<em>(</em>this<em>)</em>;<br>        this.logout = this.logout.bind<em>(</em>this<em>)</em>;<br>    <em>}<br><br>    </em>async login<em>() {<br>        </em>await this.props.oktaAuth.signInWithRedirect<em>()</em>;<br>    <em>}<br><br>    </em>async logout<em>() {<br>        </em>await this.props.oktaAuth.signOut<em>()</em>;<br>    <em>}<br><br>    </em>toggle<em>() {<br>        </em>this.setState<em>({<br>            </em>isOpen: !this.state.isOpen<br>        <em>})</em>;<br>    <em>}<br><br>    </em>render<em>() {<br>        </em>if <em>( </em>this.props.authState.isPending <em>) {<br>            </em>return <em>(<br>                &lt;</em>div<em>&gt;</em>Loading authentication...<em>&lt;/</em>div<em>&gt;<br>            )</em>;<br>        <em>} </em>else<br>        return <em>&lt;</em>Navbar color=&quot;light&quot; light expand=&quot;md&quot;<em>&gt;<br>            &lt;</em>NavbarBrand<em>&gt;</em>App Directory<em>&lt;/</em>NavbarBrand<em>&gt;<br>            &lt;</em>NavbarToggler onClick=<em>{</em>this.toggle<em>}/&gt;<br>            &lt;</em>Collapse isOpen=<em>{</em>this.state.isOpen<em>} </em>navbar<em>&gt;<br>                &lt;</em>Nav className=&quot;ml-auto&quot; navbar<em>&gt;<br><br>                    {</em>!this.props.authState.<strong>isAuthenticated</strong> ?<br>                        <em>&lt;</em>NavItem<em>&gt;<br>                            &lt;</em>Button color=&quot;secondary&quot; outline onClick=<em>{</em>this.login<em>}&gt;</em><strong>Login</strong><em>&lt;/</em>Button<em>&gt;<br>                        &lt;/</em>NavItem<em>&gt; </em>:<br>                        <em>&lt;</em>NavItem<em>&gt;<br>                            &lt;</em>Button color=&quot;secondary&quot; outline onClick=<em>{</em>this.logout<em>}&gt;</em><strong>Logout</strong><em>&lt;/</em>Button<em>&gt;<br>                        &lt;/</em>NavItem<em>&gt;<br>                    }<br><br>                &lt;/</em>Nav<em>&gt;<br>            &lt;/</em>Collapse<em>&gt;<br>        &lt;/</em>Navbar<em>&gt;</em>;<br>    <em>}<br>})</em>;</pre><h3>Packaging React.js Web App with Spring Boot JAR</h3><p>Typically, a microservice is packaged up as a single JAR file that is run in a container via a java command line. However, this isn’t as simple as it sounds because the files are not served from a static directory, but as resources in the JAR file.</p><p>This is done using the maven resources plugin via a pom.xml config:</p><pre><em>&lt;</em>plugin<em>&gt;<br>   &lt;</em>artifactId<em>&gt;</em>maven-resources-plugin<em>&lt;/</em>artifactId<em>&gt;<br>   &lt;</em>version<em>&gt;</em>3.2.0<em>&lt;/</em>version<em>&gt;<br>   &lt;</em>executions<em>&gt;<br>      &lt;</em>execution<em>&gt;<br>         &lt;</em>id<em>&gt;</em>position-react-build<em>&lt;/</em>id<em>&gt;<br>         &lt;</em>goals<em>&gt;<br>            &lt;</em>goal<em>&gt;</em>copy-resources<em>&lt;/</em>goal<em>&gt;<br>         &lt;/</em>goals<em>&gt;<br>         &lt;</em>phase<em>&gt;</em>prepare-package<em>&lt;/</em>phase<em>&gt;<br>         &lt;</em>configuration<em>&gt;<br>            &lt;</em>outputDirectory<em>&gt;</em>${project.build.outputDirectory}/static<em>&lt;/</em>outputDirectory<em>&gt;<br>            &lt;</em>resources<em>&gt;<br>               &lt;</em>resource<em>&gt;<br>                  &lt;</em>directory<em>&gt;</em>${frontend-src-dir}/../build<em>&lt;/</em>directory<em>&gt;<br>                  &lt;</em>filtering<em>&gt;</em>false<em>&lt;/</em>filtering<em>&gt;<br>               &lt;/</em>resource<em>&gt;<br>            &lt;/</em>resources<em>&gt;<br>         &lt;/</em>configuration<em>&gt;<br>      &lt;/</em>execution<em>&gt;<br>   &lt;/</em>executions<em>&gt;<br>&lt;/</em>plugin<em>&gt;</em></pre><p>The front-end-src dir is defined at the top of the pom.xml file like so if you created your React.js app in the webclient directory:</p><pre><em>&lt;</em>frontend-src-dir<em>&gt;</em>webclient/src<em>&lt;/</em>frontend-src-dir<em>&gt;</em></pre><p>The pulls all the files in webclient/build directory into your microservice’s JAR file.</p><h3>Yarn Bundling and Private Artifactory</h3><p>The webclient is built using the frontend-maven-plugin which is configured like this:</p><pre><em>&lt;</em>plugin<em>&gt;<br>   &lt;</em>groupId<em>&gt;</em>com.github.eirslett<em>&lt;/</em>groupId<em>&gt;<br>   &lt;</em>artifactId<em>&gt;</em>frontend-maven-plugin<em>&lt;/</em>artifactId<em>&gt;<br>   &lt;</em>version<em>&gt;</em>${frontend-maven-plugin.version}<em>&lt;/</em>version<em>&gt;</em></pre><pre><em>   &lt;</em>configuration<em>&gt;<br>      &lt;</em>nodeVersion<em>&gt;</em>${node.version}<em>&lt;/</em>nodeVersion<em>&gt;<br>      &lt;</em>yarnVersion<em>&gt;</em>${yarn.version}<em>&lt;/</em>yarnVersion<em>&gt;<br>      &lt;</em>workingDirectory<em>&gt;</em>${frontend-src-dir}<em>&lt;/</em>workingDirectory<em>&gt;<br>      &lt;</em>installDirectory<em>&gt;</em>${project.build.directory}<em>&lt;/</em>installDirectory<em>&gt;<br>   &lt;/</em>configuration<em>&gt;</em></pre><pre><em>   &lt;</em>executions<em>&gt;<br>      &lt;</em>execution<em>&gt;<br>         &lt;</em>id<em>&gt;</em>install-frontend-tools<em>&lt;/</em>id<em>&gt;<br>         &lt;</em>goals<em>&gt;<br>            &lt;</em>goal<em>&gt;</em>install-node-and-yarn<em>&lt;/</em>goal<em>&gt;<br>         &lt;/</em>goals<em>&gt;<br>      &lt;/</em>execution<em>&gt;</em></pre><pre><em>      &lt;</em>execution<em>&gt;<br>         &lt;</em>id<em>&gt;</em>yarn-rpm-registry<em>&lt;/</em>id<em>&gt;<br>         &lt;</em>goals<em>&gt;<br>            &lt;</em>goal<em>&gt;</em>yarn<em>&lt;/</em>goal<em>&gt;<br>         &lt;/</em>goals<em>&gt;<br>         &lt;</em>configuration<em>&gt;<br>            &lt;</em>arguments<em>&gt;</em>config set registry ${npm.registry}<em>&lt;/</em>arguments<em>&gt;<br>         &lt;/</em>configuration<em>&gt;<br>      &lt;/</em>execution<em>&gt;</em></pre><pre><em>      &lt;</em>execution<em>&gt;<br>         &lt;</em>id<em>&gt;</em>yarn-install<em>&lt;/</em>id<em>&gt;<br>         &lt;</em>goals<em>&gt;<br>            &lt;</em>goal<em>&gt;</em>yarn<em>&lt;/</em>goal<em>&gt;<br>         &lt;/</em>goals<em>&gt;<br>         &lt;</em>configuration<em>&gt;<br>            &lt;</em>arguments<em>&gt;</em>install<em>&lt;/</em>arguments<em>&gt;<br>            &lt;</em>yarnInheritsProxyConfigFromMaven<em>&gt;</em>false<em>&lt;/</em>yarnInheritsProxyConfigFromMaven<em>&gt;<br>         &lt;/</em>configuration<em>&gt;<br>      &lt;/</em>execution<em>&gt;</em></pre><pre><em>      &lt;</em>execution<em>&gt;<br>         &lt;</em>id<em>&gt;</em>build-frontend<em>&lt;/</em>id<em>&gt;<br>         &lt;</em>goals<em>&gt;<br>            &lt;</em>goal<em>&gt;</em>yarn<em>&lt;/</em>goal<em>&gt;<br>         &lt;/</em>goals<em>&gt;<br>         &lt;</em>phase<em>&gt;</em>prepare-package<em>&lt;/</em>phase<em>&gt;<br>         &lt;</em>configuration<em>&gt;<br>            &lt;</em>arguments<em>&gt;</em>build<em>&lt;/</em>arguments<em>&gt;<br>            &lt;</em>yarnInheritsProxyConfigFromMaven<em>&gt;</em>false<em>&lt;/</em>yarnInheritsProxyConfigFromMaven<em>&gt;<br>         &lt;/</em>configuration<em>&gt;<br>      &lt;/</em>execution<em>&gt;<br>   &lt;/</em>executions<em>&gt;<br>&lt;/</em>plugin<em>&gt;</em></pre><p>We mirror our NPM artifacts as well as Node.js versions in our artifactory and that’s what the “config set registry” takes care of.</p><p>Also be sure to set this on your local machine because Yarn’s package.json includes hardcoded URLs to the NPM artifacts. If you don’t reference them from your artifactory, they’ll use the public npmjs artifactory which may not be accessible from your CI pipeline.</p><h3>Conclusion</h3><p>I hope that gives you enough tips to get around speed bumps you might encounter getting JWT logins to work with Spring Boot and an SPA app. I sure wish I had seen these tips while working on my project :-)</p><p>p.s. this was the first production Kotlin microservice at our company and I’m happy to report I didn’t encounter any issues writing it in Kotlin. Big thanks to <a href="https://medium.com/u/300e902fbc09">Sébastien Deleuze</a>’s hard work for helping make it so seamless.</p><h3>References</h3><ul><li><a href="https://developer.okta.com/blog/2019/04/15/testing-spring-security-oauth-with-junit">https://developer.okta.com/blog/2019/04/15/testing-spring-security-oauth-with-junit</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cbe19227c1be" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Chocolatey Windows and Ctrl/Alt AutoHotKey]]></title>
            <link>https://kenkyee.medium.com/chocolatey-windows-and-ctrl-alt-autohotkey-f263c9ea3c80?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/f263c9ea3c80</guid>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[windows-10]]></category>
            <category><![CDATA[graalvm]]></category>
            <category><![CDATA[chocolatey]]></category>
            <category><![CDATA[autohotkey]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Sat, 21 Nov 2020 19:27:38 GMT</pubDate>
            <atom:updated>2020-12-13T01:36:58.774Z</atom:updated>
            <content:encoded><![CDATA[<p>Can’t believe I missed the whole <a href="https://chocolatey.org/">Chocolatey</a> package manager revolution on Windows. It’s similar to Homebrew on OSX and lets you manage your command line tools a lot more easily. Install in an admin powershell with:</p><blockquote>Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString(&#39;<a href="https://chocolatey.org/install.ps1&#39;">https://chocolatey.org/install.ps1&#39;</a>))</blockquote><p>Then you can install a bunch of useful stuff with choco:</p><blockquote>choco install gradle<br>choco install graalvm<br>choco install cloc<br>choco install putty<br>choco install autohotkey</blockquote><p>cloc is a useful tool for counting the lines of code in your codebase.</p><p>AutoHotkey is a useful tool for mapping specific Alt key combos to Ctrl key combos because it involves less contorting of your thumb (the Mac layout swaps the ctrl-alt keys which is more comfortable). You normally just need a subset of keys mapped instead of actually swapping the ctrl/alt keys though. This example is for mapping the usual editing keys as well as adding home/end/pageup/pagedown to laptop keyboards that are missing those keys (e.g. the Asus Zephyrus G14). Place this file in C:\Users\&lt;username&gt;\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\fixkeys.ahk:</p><blockquote>#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.<br>; #Warn ; Enable warnings to assist with detecting common errors.<br>SendMode Input ; Recommended for new scripts due to its superior speed and reliability.<br>SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.<br>Control &amp; Left::Send {Home}<br>Control &amp; Right::Send {End}<br>Control &amp; Up::Send {PgUp}<br>Control &amp; Down::Send {PgDn}<br>;stack keys with the same modifiers<br>; Saving<br>!s::<br>; Selecting all<br>!a::<br>; Copying<br>!c::<br>; Pasting<br>!v::<br>; Cutting<br>!x::<br>; Opening<br>!o::<br>; Finding<br>!f::<br>; Undo<br>!z::<br>; Redo<br>!y::<br>; New tab<br>!t::<br> ; close tab<br>!w::<br>; Jump to definition in IntelliJ<br>!b::<br> ; add bookmark<br>!d::send % &quot;^{&quot; substr(a_thishotkey, 2) &quot;}&quot;</blockquote><p>Another neat feature in Windows 10 is the Task Scheduler. You can do cool stuff like detect when your laptop goes into battery mode and run a script. On gaming laptops, it’s useful to do this to turn off the discrete GPU to maximize battery life. This is an example task (put it into a file with a .xml extension) that can be imported into Task Scheduler:</p><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-16&quot;?&gt;<br>&lt;Task version=&quot;1.2&quot; xmlns=&quot;<a href="http://schemas.microsoft.com/windows/2004/02/mit/task">http://schemas.microsoft.com/windows/2004/02/mit/task</a>&quot;&gt;<br>      &lt;Triggers&gt;<br>        &lt;EventTrigger&gt;<br>          &lt;Enabled&gt;true&lt;/Enabled&gt;<br>          &lt;Subscription&gt;&amp;lt;QueryList&amp;gt;&amp;lt;Query Id=&quot;0&quot; Path=&quot;System&quot;&amp;gt;&amp;lt;Select Path=&quot;System&quot;&amp;gt;*[System[Provider[<a href="http://twitter.com/Name">@Name</a>=&#39;Microsoft-Windows-Kernel-Power&#39;] and EventID=105]]&amp;lt;/Select&amp;gt;&amp;lt;/Query&amp;gt;&amp;lt;/QueryList&amp;gt;&lt;/Subscription&gt;<br>        &lt;/EventTrigger&gt;<br>      &lt;/Triggers&gt;<br>      &lt;Settings&gt;<br>        &lt;MultipleInstancesPolicy&gt;IgnoreNew&lt;/MultipleInstancesPolicy&gt;<br>        &lt;DisallowStartIfOnBatteries&gt;false&lt;/DisallowStartIfOnBatteries&gt;<br>        &lt;StopIfGoingOnBatteries&gt;false&lt;/StopIfGoingOnBatteries&gt;<br>        &lt;AllowHardTerminate&gt;true&lt;/AllowHardTerminate&gt;<br>        &lt;StartWhenAvailable&gt;true&lt;/StartWhenAvailable&gt;<br>        &lt;RunOnlyIfNetworkAvailable&gt;false&lt;/RunOnlyIfNetworkAvailable&gt;<br>        &lt;IdleSettings&gt;<br>          &lt;StopOnIdleEnd&gt;false&lt;/StopOnIdleEnd&gt;<br>          &lt;RestartOnIdle&gt;false&lt;/RestartOnIdle&gt;<br>        &lt;/IdleSettings&gt;<br>        &lt;AllowStartOnDemand&gt;true&lt;/AllowStartOnDemand&gt;<br>        &lt;Enabled&gt;true&lt;/Enabled&gt;<br>        &lt;Hidden&gt;false&lt;/Hidden&gt;<br>        &lt;RunOnlyIfIdle&gt;false&lt;/RunOnlyIfIdle&gt;<br>        &lt;WakeToRun&gt;false&lt;/WakeToRun&gt;<br>        &lt;ExecutionTimeLimit&gt;PT1H&lt;/ExecutionTimeLimit&gt;<br>        &lt;Priority&gt;7&lt;/Priority&gt;<br>      &lt;/Settings&gt;<br>      &lt;Actions Context=&quot;Author&quot;&gt;<br>        &lt;Exec&gt;<br>          &lt;Command&gt;Powershell.exe&lt;/Command&gt;<br>          &lt;Arguments&gt;-ExecutionPolicy Bypass C:\Users\me\UseIGP.ps1 -RunType $true -Path C:\Users\kenky\&lt;/Arguments&gt;<br>          &lt;WorkingDirectory&gt;C:\Users\kenky&lt;/WorkingDirectory&gt;<br>        &lt;/Exec&gt;<br>      &lt;/Actions&gt;<br>&lt;/Task&gt;</pre><p>And put this script in your C:\Users\me\UseIGP.ps1 file if you have a Zephyrus G14:</p><pre>## run this script when going off A/C power</pre><pre>## kill nVidia processes 3X to make sure they don&#39;t resurrect<br>Get-Process | Where-Object {$_.Path -like &quot;*NVIDIA*&quot;} | Stop-Process -Force<br>Get-Process | Where-Object {$_.Path -like &quot;*NVIDIA*&quot;} | Stop-Process -Force<br>Get-Process | Where-Object {$_.Path -like &quot;*NVIDIA*&quot;} | Stop-Process -Force</pre><pre>## kill off Epic games launcher<br>Get-Process | Where-Object {$_.Path -like &quot;*Epic Games*&quot;} | Stop-Process -Force</pre><pre>## kill off Steam games launcher<br>Get-Process | Where-Object {$_.Path -like &quot;*Steam*&quot;} | Stop-Process -Force</pre><pre>## this disables the dGPU...<br>Disable-PnpDevice -InstanceId (Get-PnpDevice -FriendlyName *2060* -Status OK).InstanceId -Confirm:$false<br>Enable-PnpDevice -InstanceId (Get-PnpDevice -FriendlyName *2060* ).InstanceId -Confirm:$false</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f263c9ea3c80" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Optional<T> in Kotlin using a Sealed Class]]></title>
            <link>https://kenkyee.medium.com/optional-t-in-kotlin-using-a-sealed-class-584113576f74?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/584113576f74</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[sealed-classes]]></category>
            <category><![CDATA[optionals]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Sun, 12 Apr 2020 21:30:10 GMT</pubDate>
            <atom:updated>2020-04-12T21:30:10.464Z</atom:updated>
            <content:encoded><![CDATA[<p>Nice quick way to add Guava or Android API24’s Optional&lt;T&gt; using Kotlin’s sealed classes (until they build this into the languate as a Maybe class):</p><pre>sealed class Option<em>&lt;</em>out A<em>&gt; {<br>    </em>object None : Option<em>&lt;</em>Nothing<em>&gt;()<br>    </em>data class Value<em>&lt;</em>out A<em>&gt;(</em>val value: A<em>) </em>: Option<em>&lt;</em>A<em>&gt;()<br><br>    </em>companion object <em>{<br>        </em>fun <em>&lt;</em>A<em>&gt;</em>from<em>(</em>value: A?<em>)</em>: Option<em>&lt;</em>out A<em>&gt; {<br>            </em>return value?.<em>let </em><strong>{ </strong>Value<em>(</em>value<em>) </em><strong>} </strong>?: None<br>        <em>}<br>    }<br>}</em></pre><p>Usage is pretty simple…just do Option.from&lt;YourClass&gt; on any value that may be nullable. Then you can use this to pass through a reactive stream since you can’t pass nulls through; on the other wise, unwrap it with:</p><pre>when (it) {<br>     is Value -&gt;<br>           val value = it.value<br>     is None -&gt;<br>           // handle error<br>}</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=584113576f74" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introspective on Being an Architect, Tech Lead, Team Lead]]></title>
            <link>https://kenkyee.medium.com/introspective-on-being-an-architect-tech-lead-team-lead-9bafef324d7d?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/9bafef324d7d</guid>
            <category><![CDATA[mentoring-matters]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[teamwork]]></category>
            <category><![CDATA[leadership-development]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Sat, 04 Jan 2020 21:12:55 GMT</pubDate>
            <atom:updated>2020-01-28T15:59:22.488Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/564/0*RTJVCpAAp0YjGSzW.jpg" /><figcaption>(from <a href="https://www.careermetis.com/essential-qualities-team-leader/">https://www.careermetis.com/essential-qualities-team-leader/</a>)</figcaption></figure><p>It’s the end of another year and a time to reflect on the meaning of life (also known as annual reviews). This is my third company that has had 360 Degree Reviews and writing a self review always has meant a deeper self reflection/introspective that I usually don’t have time to do during the course of the year.</p><p>When I left KAYAK earlier this year, I wrote a farewell/thanks memo and unusually signed off with a few nicknames that I was surprised no one asked about before I left (though the team lead did hilariously ask whether I misspelled the CEO’s name deliberately which I guess management believed because my farewell lunch was at the mall food court :-)</p><p>Little did I know, these nicknames were attributes that Wayfair quantified and documented as things that are expected of their architects. The nickname a.k.a. list included:</p><p>“I Bleed So My Team Doesn’t Have To”</p><p>For taking care of issues while the teammates relax (e.g, fixing a crash spike in code I didn’t write during Thanksgiving).</p><p>For testing all the alphas/betas of tools before they need to use them including Android Studio, Gradle, and Kotlin (and setting up ktLint/Detekt to keep help them transition from Java).</p><p>For helping fix CI/CD issues so the team can operate smoothly.</p><p>“Iceberg Navigator”</p><ul><li>For trying to steer everyone towards the future without hitting icebergs on the way.</li><li>For pushing hard to get Kotlin into the app (and server-side but they steadfastly refused to use Kotlin even though over the next year, Spring added huge amounts of support for it thanks to Sebastian Deleuze) before and after Google approved it.</li><li>For steering the use of Android Arch Components as our official architecture from a mix of BBOM (Big Ball of Mud) and an acquisition’s MVP architecture.</li><li>For trying to add UI integration testing two years before it was asked for by management.</li><li>For working on early features like App Slices before management asks because it looked important to the business.</li><li>For warning our team and legal about GDPR in June 2018 because other app devs were freaking out over it, but we weren’t told to implement it until Jan 2019 when a <a href="https://skift.com/2019/01/02/popular-travel-apps-shared-detailed-user-information-with-facebook/">privacy story blew up</a>.</li></ul><p>“Google Liason”</p><ul><li>For working with Google on various Early Access Programs like Google Pay and Firebase performance and helping improve their UX and APIs.</li><li>For working with the Crashlytics/Beta folks on testing their migration tools to Firebase and helping improve their UX via various focus group sessions.</li><li>For pre-alpha testing various Android libraries including the Arch Nav Components and Arch ViewModel SavedState to see whether they should be used in our architecture.</li><li>Filed bug reports with Google and provided repro code.</li></ul><p>“Bridge Builder”</p><ul><li>For working with the API team by helping test their early checkout APIs and reviewing code and identifying APIs that could be improved.</li><li>For working with the iOS/mWeb teams by comparing API usage with them and warning them when finding idiosyncrasies.</li><li>For bridging the Android developers in Berlin by getting up early to help with anything they needed before my Cambridge coworkers were awake.</li><li>For building bridges to the acquired Momondo Copenhagen Android team to acclimate them to our development culture.</li><li>For bridging the UX team by helping them understand what is needed for Android Vector Drawables, Android Material Theming, and Tablet Layouts; and by attending an After Effects course with them to improve their understanding of Lottie.</li><li>For bridging w/ the Data Science team in a great in-house data science course to see whether we can apply ML to predictions in-app by looking at device usage to see when people would like to travel. It didn’t hurt that our mentor from the DS team was an avid Kaggler…we ended up winning the competition at the end thanks to Remi ;-)</li></ul><p>“App Janitor”</p><ul><li>Because being an architect, tech lead, team lead is not always a glamorous job.</li><li>For cleaning up 3000+ lint warning despite management saying “no one looks at them so why bother”.</li><li>For updating all the libraries that were years up to date and doing multiple 300–2300 file PRs to bring the app up to date.</li><li>For bringing our crash rate from 96% to 99.96%.</li><li>For providing a better way to help solve crashes than just looking at stack traces.</li></ul><p>This can be summarized into these simple guiding principles that I’ve always lived my life by:</p><ul><li>Always help others become better because it makes you better: mentor, do code reviews, help out in “not your expertise” areas, write wikis, write blogs, do presentations, help at local meetups and conferences.</li><li>Never stop learning because you never know everything and the more you learn, the better you can see; share what you learn (see the previous point) because it makes people around you better.</li><li>Be a good role model so people can respect you and follow your lead; always be professional, courteous and friendly and call out things that are illegal or morally questionable.</li></ul><p>These are all attributes anyone with these titles above should have and should cultivate…they’re definitely ones I’ve tried to improve and live by through my career.</p><p>Hope everyone has a great New Year!</p><p>p.s. yes it was a typo…one ‘f’ not two. I’d blame it on the crappy Mac butterfly keyboard but I didn’t have one at the time :-)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9bafef324d7d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AndroidX Migration Tips]]></title>
            <link>https://kenkyee.medium.com/the-google-android-ktx-library-contains-a-set-of-useful-kotlin-extensions-that-make-writing-ee03c634df60?source=rss-9548dc0b14c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/ee03c634df60</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[migration]]></category>
            <category><![CDATA[androidx]]></category>
            <dc:creator><![CDATA[Ken Yee]]></dc:creator>
            <pubDate>Wed, 17 Apr 2019 18:42:01 GMT</pubDate>
            <atom:updated>2019-04-19T01:00:13.264Z</atom:updated>
            <content:encoded><![CDATA[<p>The <a href="https://developer.android.com/kotlin/ktx">Google Android KTX library</a> contains a set of useful Kotlin extensions that make writing Android code shorter and less error prone. Unfortunately, it won’t be backported so it’s usable on a non-AndroidX project. Other libraries like okhttp/retrofit have also started releasing AndroidX versions of their libraries but not backporting features/bugs. Pretty much any new Google Android library is only going to support AndroidX (e.g. KTX extensions) and any bug fixes will be going only into the AndroidX versions, so you’ll end up doing this sooner than later ;-)</p><p>Here are a few tips after migrating a few projects to AndroidX:</p><h3>Use the MigrateToAndroidX Refactor only on build.gradle files</h3><p>The built-in Android Studio Refactor/MigrateToAndroidX tool is a memory hog and adds AndroidX package prefixes to your code instead of only renaming the import lines. E.g., you’ll see stuff like this:</p><blockquote>androidx.recyclerview.widget.RecyclerView cartList = findViewById&lt;androidx.recyclerview.widget.RecyclerView&gt;(R.id.recycler)</blockquote><p>This is hard to find in the initial big commit (for one of the projects I converted, it was 2300 files); it will also do this for other random classes like the ViewPager and FragmentManager. The best thing to do is use the tool to update your build.gradle files which it does well with, then revert the rest of the changes and use a renaming script (see below).</p><p>The Refactor/MigrateToAndroidX tool also has memory issues if you have a large project because they tried to speed it up from the slowish version in Android Studio 3.3; if you can’t increase the heap space in Android Studio 3.4/3.5, try the previous version. Or you can just change your build.gradle by hand and run the renaming script below instead.</p><p>Be sure to check all your build.gradle files afterwards. If you use a nice clean .ext block in your top level build.gradle to version everything, you’ll find that the migration tool hardcoded all the library versions in all of your modules to this :-(</p><blockquote>kapt <strong>&quot;androidx.room:room-compiler:2.0.0&quot;</strong></blockquote><h3>Get/Use a package renaming script</h3><p>As mentioned above, the built-in refactor tool has issues in getting a little overzealous in prepending package paths (probably because it leans on the Android Studio renaming refactor framework). In most cases, you’ll be doing the AndroidX migration in a branch and every time you merge master/dev into your branch, you’ll have to run the package renaming script before you can rebuild if someone has added any references to non-AndroidX classes.</p><h3>WrongConstant Lint Check Broken</h3><p>The WrongConstant AndroidX lint check is plain broken once you finish the migration. You should add a</p><blockquote>&lt;issue id=”WrongConstant” severity=”ignore” /&gt;</blockquote><p>line in your app’s lint.xml. Star this issue to see when it gets fixed: <a href="https://issuetracker.google.com/issues/119753493">https://issuetracker.google.com/issues/119753493</a></p><h3>Jetifier Partially Jets</h3><p>Some libraries that are nested don’t seem to get Jetified (converted to AndroidX) automatically. One example is the uiautomator library which was indirectly referenced by fastlane as well as included in our build.gradle: <a href="https://issuetracker.google.com/issues/123060356">https://issuetracker.google.com/issues/123060356</a></p><p>The fix is fairly simple…exclude the artifact out of the other library:</p><blockquote>androidTestImplementation (‘tools.fastlane:screengrab:1.2.0’) {<br> // <a href="https://issuetracker.google.com/issues/123060356">https://issuetracker.google.com/issues/123060356</a><br> exclude group: ‘com.android.support.test.uiautomator’, module: ‘uiautomator-v18’<br>}</blockquote><h3>InstrumentationRegistry Application References</h3><p>The way you get the reference to your custom application class is different. Instead of using getTargetContext() (which is the test application) in a test, you have to do:</p><blockquote>InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestApplication</blockquote><h3>References</h3><p>Thanks to Dan Lew’s original article on this process: <a href="https://blog.danlew.net/2018/11/14/the-reality-of-migrating-to-androidx/">https://blog.danlew.net/2018/11/14/the-reality-of-migrating-to-androidx/</a></p><p>And AndiMiko for writing a Python version of Dan’s script, which I’ve hardened a bit and added a few more flags for testing to:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/974c2b22fd8cda8ec6519a29a7ed0fec/href">https://medium.com/media/974c2b22fd8cda8ec6519a29a7ed0fec/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ee03c634df60" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>