<?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 Stacy Devino on Medium]]></title>
        <description><![CDATA[Stories by Stacy Devino on Medium]]></description>
        <link>https://medium.com/@DoesitPew?source=rss-2ffcf6104135------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*XCD_72a0UVAXtJyD.png</url>
            <title>Stories by Stacy Devino on Medium</title>
            <link>https://medium.com/@DoesitPew?source=rss-2ffcf6104135------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 09:02:25 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@DoesitPew/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[The Better WebViews Collection]]></title>
            <link>https://proandroiddev.com/the-better-webviews-collection-778c7bd709e6?source=rss-2ffcf6104135------2</link>
            <guid isPermaLink="false">https://medium.com/p/778c7bd709e6</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Stacy Devino]]></dc:creator>
            <pubDate>Tue, 09 May 2023 14:04:27 GMT</pubDate>
            <atom:updated>2023-07-03T03:54:12.349Z</atom:updated>
            <content:encoded><![CDATA[<h3>The Better Android WebViews Collection</h3><p>WebViews are one of the most controversial subjects in the Community and unfortunately have been much maligned by developers. WebViews can be extremely useful and the best first implementation for a ton of situations from Login and Payments to User Support and Legal. Using WebViews, you can allocate your resources and attention to what your customers value most. WebView-primary apps even make up almost a third of all Apps on the Google Play Store.</p><p><em>So, how do we make really good WebViews?</em></p><h3>Knowledge Drop 🧠💧</h3><ol><li>WebKit implementation varies<br>⏺ Non-Chromium-based (Non-GMS devices and some vendors)<br>⏺ Not all Play-Enabled devices have all features. Use isFeatureSupported and permission checks rather than @Targetan Android OS to be safe.<br>⏺ Customized Chromium Base (Samsung and <a href="https://developer.amazon.com/docs/fire-tv/hybrid-apps-overview.html#amazon-webview-awv">Amazon Fire devices</a>)</li><li>There are 3 “Standard” WebViews available embedded as <a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/webview-providers.md">providers</a>. <br>⏺ “<a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/aosp-system-integration.md#standalone-webview">Standalone</a>” AOSP basic implementation and standard for 5.X-6.X<br>⏺ “<a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/aosp-system-integration.md#monochrome">Monochrome</a>” public and AOSP-accessible unified Browser and WebView for 7.X-9.X<br>⏺ “<a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/aosp-system-integration.md#trichrome">Trichrome</a>” which is a base Core Library, WebView, and Chrome browser all separated for 10.X — Now (13.X)</li><li>Use the modern WebViewCompat extensions with <a href="https://developer.android.com/jetpack/androidx/releases/webkit">AndroidX Webkit</a> / <a href="https://developer.android.com/jetpack/androidx/releases/browser">AndroidX Browser</a> to avoid compatibility issues &amp; version-based errors</li><li>All WebView instances in your app will share the same Cache and Dom Storage, improving performance and memory usage</li><li>Trust the Chromium Docs first over android.dev<br>⏺ <a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/README.md">WebView is now built and maintained by the Chrome team, so their documentation is going to be the most accurate and extensive</a></li></ol><h3>WebView Settings for Success 🏆</h3><p><strong>“Make it look like Chrome” or “Why is Scaling broken?!?!?!”</strong></p><p>ReactJS, VueJS and similar front end frameworks rely on viewport and framing information in order to enable their dynamic sizing and styling features. By Default, what exists on Chrome is not what is enabled in the WebView and it requires some browser/web rendering knowledge to even figure out what is going wrong. <a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/web-page-layout.md#configuring-webview-like-a-browser">Try these out to get the flags that Chrome uses by default</a> (<em>And the CLI WebView Shell shares Chrome default flags, which is infuriating when trying to Test a problem!!!</em>).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UcOzFRUtpBgL2ip1zXVjcg.jpeg" /></figure><p>*This can be KEY for A11Y where Text and Scaling has been enlarged and especially on 720p screens as often users will lose parts of the webpage.</p><pre>WebView.settings.apply{<br>  layoutAlgorithm = WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING<br>  loadWithOverviewMode = true<br>  useWideViewPort = true<br>}</pre><p><strong>“The WebView page didn’t pass our Security Tests”</strong></p><p>You have too much enabled and probably think you need features that you don’t anymore, especially for upload and hybrid interactions <br>(web &lt;-&gt; native).</p><ol><li>Dom Storage — aka Local Storage is the key-value store available to browsers which they sometimes use to manage state, but is also completely unprotected.</li><li>Javascript — I encourage you to test if you actually need it for your application, as some pages may not need this. <a href="https://developer.android.com/develop/ui/views/layout/webapps/webview#UsingJavaScript">You do need this for Javascript &lt;-&gt; Native app interactions and captures</a>. Otherwise, you could just leaving the ability to execute code here open.</li><li>Files — Ton of settings here, but the odds are that unless you are doing something like a PDF or ePub reader they are usually not needed. You absolutely can still do WebView upload with these disabled. If you need to to load local or downloaded assets, use <a href="https://developer.android.com/reference/androidx/webkit/WebViewAssetLoader">WebViewAssetLoader</a>.</li><li>Safebrowsing — Don’t load any ClearText Urls (http://) ever, just… no</li></ol><pre>WebView.settings.apply{<br>  domStorageEnabled = false //Careful here as your Website might need this<br>  javaScriptEnabled = false //Check if you actually need this<br>  allowContentAccess = false<br>  allowFileAccess = false<br>  allowFileAccessFromFileURLs = false<br>  allowUniversalAccessFromFileURLs = false<br>  safeBrowsingEnabled = true //API 26+ only, use the Compat version below!<br>}<br><br>if(WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_ENABLED)){<br>  WebSettingsCompat.setSafeBrowsingEnabled(WebView.settings, true)<br>}</pre><p><em>BONUS:</em> Did you know that shouldOverrideUrlLoading() only works on get requests? So, its up to do you to your own validation before loadUrl too.</p><p><strong>“Why is this scroll not 60FPS tho?”</strong></p><ol><li>Optimizing your Web Code will do more than optimizing your WebView<br>⏺ Minify Code<br>⏺ Strip your CSS and packages to what you just need for those pages<br>⏺ Image services that scale to the device/window being serviced</li><li>More Flags for your profit</li></ol><pre>WebView.settings.apply{<br>  loadsImagesAutomatically = true //default value but should declare too<br>  offscreenPreRaster = true //only API 23+, use Compat version below!<br>  setSupportZoom(false) //disable to load scale to window size, but disables zoom :(<br>}<br><br>if(WebViewFeature.isFeatureSupported(WebViewFeature.OFF_SCREEN_PRERASTER)){<br>  WebSettingsCompat.setOffscreenPreRaster(WebView.settings, true)<br>}</pre><p>3. Think about your <a href="https://developer.android.com/reference/android/webkit/WebSettings#setCacheMode(int)">Caching Strategy</a> (<a href="https://developer.android.com/reference/android/webkit/WebSettings#LOAD_DEFAULT">LOAD_DEFAULT</a>, <a href="https://developer.android.com/reference/android/webkit/WebSettings#LOAD_NO_CACHE">LOAD_NO_CACHE</a>, <a href="https://developer.android.com/reference/android/webkit/WebSettings#LOAD_CACHE_ELSE_NETWORK">LOAD_CACHE_ELSE_NETWORK</a>, <a href="https://developer.android.com/reference/android/webkit/WebSettings#LOAD_CACHE_ONLY">LOAD_CACHE_ONLY</a>) and what the tradeoffs are for your implementation. Cache is powerful.</p><pre>WebView.settings.setCacheMode(WebSettings.LOAD_DEFAULT)</pre><h3>Testing Your WebViews 🧪</h3><p><strong>“The Web Team needs something that they can test with on CLI”</strong></p><p>Introducing <a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/webview-shell.md">System WebView Shell</a> which you can build yourself (with your own settings) or use one of the pre-built APKs available <a href="https://storage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Android/">HERE</a>.</p><p>This is the package used by the Compatibility Test Suite and should be a good baseline. <em>Remember though, by default it comes with the </em><em>WebView.Settings that are used by Chrome and not all the default values assigned in AOSP base.</em></p><p>Some good instructions on gathering traces for reporting are found for <a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/test-instructions.md#debugging-hangs-in-instrumentation-tests">Instrumented Tests here</a> and <a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/net-debugging.md">Logging of network data here</a>.</p><p><strong>“How do I test it in the App with Tools?”</strong></p><p>You can use Chrome Dev Tools with your Android App WebViews.</p><p><em>There are some limitations though not present in usual Chrome.</em><br> — You cannot change executing code in the window (edits mean nothing)<br> — Code execution breakpoints work, but might be more flaky for inspection<br> — User Gestures and object Inspection don’t really work through the view port</p><ol><li>Make your WebViews Debuggable (only valid API 19+)<br><em>*Be smart </em>🧐 <em>and put this in your </em><em>debug app build setting parameters.</em></li></ol><pre>WebView.setWebContentsDebuggingEnabled(true)</pre><p>Now, you can run your app and connect to that active WebView by going to chrome://inspect/#devices in Chrome and tapping the inspect button under your device. <em>This works for Emulators and Real Devices on the same local network.</em></p><p>2. Network Settings to make it work for ClearText urls and Proxying<br>*Odds are if you are testing a web application routed through local host or another port locally, you will need to enable insecure URLs or Proxying. The default setting for Android is to have ClearText disabled in all cases.</p><p>Add this to your network_security_config.xml</p><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;<br>&lt;network-security-config&gt;<br>  &lt;!-- Debug Override considerations --&gt;<br>  &lt;debug-overrides cleartextTrafficPermitted=&quot;true&quot;&gt;<br>    &lt;trust-anchors&gt;<br>       &lt;certificates src=&quot;user&quot; /&gt;<br>       &lt;certificates src=&quot;system&quot; /&gt;<br>    &lt;/trust-anchors&gt;<br>  &lt;/debug-overrides&gt;</pre><p>3. Add the AndroidX <a href="https://developer.android.com/reference/kotlin/androidx/webkit/TracingController">Tracing Controller</a> for tracing deeper into the code at runtime and performance profiling</p><pre>if (WebViewFeature.isFeatureSupported(<br>    WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE)) {<br>  val tracingController = TracingController.getInstance()<br>  tracingController.start(<br>      TracingConfig.Builder().addCategories(CATEGORIES_WEB_DEVELOPER).build()<br>  )<br>}<br>....<br>//Optional<br>tracingController.stop(FileOutputStream(&quot;trace.json&quot;), Executors.newSingleThreadExecutor())</pre><p><strong>“Unit Test all the things!”</strong></p><p><a href="https://chromium.googlesource.com/chromium/src/+/HEAD/testing/android/docs/README.md">Some of the Best Instructions and Samples on how to do this well again live in the Docs.</a></p><p>Take a Look at the <a href="https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/test-instructions.md#chromium_side-tests">AOSP Tests</a> as well as the SourceCode for AndroidX <a href="https://android.googlesource.com/platform/frameworks/support/+/androidx-main/browser">Browser</a> and <a href="https://android.googlesource.com/platform/frameworks/support/+/androidx-main/webkit/">Webkit</a>.</p><h3>Hybrid Apps</h3><p><strong>“My WebViews need to talk to my App and my App needs to talk to my WebViews” 👉👈</strong></p><p>Time to skip and stop using the ol’ JavascriptInterface standard for Web &lt;-&gt; App communication and start using the <a href="https://html.spec.whatwg.org/multipage/web-messaging.html#messagechannel">HTML5 MessageChannel</a> spec because we care about user security and message integrity! This spec works on iOS too.</p><p>This is a dedicated set of ports for doing posting and receiving of WebMessage. For obvious reasons, please use WebMessageCompat to avoid compatibility issues.</p><pre>val uri = Uri.parse(&quot;https://developer.android.com/develop/ui/views/layout/webapps&quot;)<br>// Check if this feature is supported, <br>// Don&#39;t use Android API versions as they aren&#39;t guaranteed<br>if (WebViewFeature.isFeatureSupported(<br>    WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL)){<br>   createMessageChannel()<br>}<br><br>fun createMessageChannel(){<br>  val channel = webView.createMessageChannel() //Creates 2 ports <br>  val recievePort = channel[0]<br>  val sendPort = channel[1]<br>  //Receive on port 0<br>  recievePort.setWebMessageCallback(<br>              WebMessagePortCompat.WebMessageCallBackCompat(){<br>    override fun onMessage(port: WebMessagePortCompat, <br>                           message: WebMessageCompat?){<br>      // Received Message<br>    } <br>  })<br>  //Send on port 1<br>  webView.postWebMessage(WebMessageCompat(&quot;a message&quot;, <br>                          WebMessagePortCompat[]{sendPort}, uri))<br>}</pre><p>Some Caveats to be aware of:</p><ol><li>This only works on Loaded Pages, so no “startup” code can be gathered here. Must wait for onPageFinished() to be called inside of <a href="https://developer.android.com/reference/androidx/webkit/WebViewClientCompat">WebViewClientCompat</a></li><li>Android 6.0+ only, not supported on older.</li><li>file://asset_url doesn’t really work here, so if that is your content (ePub Readers and PDF loaders for example) use webview.loadDataWithBaseUrl() instead of loadUrl().</li></ol><p><strong>“I need my web page to only load the Android || iOS version”</strong></p><p><em>Did you know that you can grab Javascript </em><em>Boolean parameters from Android AND iOS (WKWebView) PRIOR TO THE </em><em>onDocumentReady() CYCLE IN JAVASCRIPT?!</em></p><p>No really, and I have done it to build ALL KINDS of WebViews that “Feel Native” and it’s a solid alternative to custom URL parameters.</p><p>Basically, you need to do a Javascript query against the window or a custom extension ala window.mobileInterface for the JS Object. Then just assign boolean values to your JavascriptInterface function that maps to in Android 👇.</p><pre>class MyJSInterface(){<br>  @JavascriptInterface<br>  fun isAndroid():Boolean { return true }<br>}<br><br>val extJSConst = &quot;window.mobileInterface&quot;</pre><p>And then just add WebView.addJavascriptInterface(MyJSInterface, extJSConst)</p><p>which means that window?.mobileInterface.isAndroid() from Javascript would returntrue || null with null being the fail-over.</p><p>Since that can be executed prior to onDocumentReady , it means you can set objects to render and not to render in your JS code. This means that the end user will never see a flashing screen of things turning off, execution is faster and streamlined, and you can customize your “Mobile-first” experience without messing with your other web code.</p><p>In my opinion, this is just about the only acceptable use of @JavascriptInterface in modern apps because manipulation through MITM security or data concerns just simply don’t exist.</p><p><strong>“I need my WebViews to be logged in.” / “Why are my users getting logged out?”</strong></p><p>We need to use the <a href="https://developer.android.com/reference/android/webkit/CookieManager">CookieManager</a>. This is because most login sessions for Web Apps use Cookies that contain their Token to manage their session validity.</p><p>Cookies are managed by default in the caching scheme you defined earlier and that is self managing. If your login is also a WebView (you should consider using a <a href="https://github.com/GoogleChrome/android-browser-helper">Chrome Custom Tab if you are a hybrid app</a>), the WebView should receive the cookie and “it just works” with regards to your WebViews. If not, you will need to grab your Logged-In token and generate a Cookie or have a Cookie Generator API in your system which you then set below</p><pre>val cookieManager = CookieManager.getInstance()<br>cookieManager.setCookie(myUrl, myCookieString, null) //Can use ValueCallback instead of null</pre><p>This automatic web and cache management does mean that when the user logged in session has timed out either from an invalid session or an expired cookie, it will kick the user out to log in again. This does not align with mobile user expectations where the user has logged in and the app remembers in-perpetuity. This is why users might be complaining about being “logged out” and more importantly for a business, could be negatively affecting your notifications and metrics.</p><p>So, let’s save our Logged-In token and store our existing Cookies to put back when the app is restarted. Or, just the cookies we care about most. Now, how you save and store this is very much going to depend on your application and I would encourage you to look at options like making an encrypted <a href="https://developer.android.com/topic/libraries/architecture/datastore">DataStore</a> or type-safe <a href="https://developer.android.com/jetpack/androidx/releases/datastore#proto-datastore-dependencies">Proto DataStore</a>. Don’t worry, invalidation on the web side will still work correctly if you have a user return outside of the window or if you had to do an internal rotation.</p><pre>val cookieManager = CookieManager.getInstance()<br>val cookiesString = cookieManager.getCookies()<br>//Store cookies how you see fit....<br>cookieManager.setCookie(myUrl, cookiesString, null) </pre><h3>Conclusion</h3><p>Not everything needs to or should be fully-Native Mobile. We can build great web experiences in our Apps for our users when we put in the work.</p><p>So, I encourage you to think deeply about whether those “Terms and Conditions” popups should be accessed and maintained in an API or simply route to your Url? Do you really want to build all of your customer support pages natively as well? Is this a good use of our resources to build this out natively? If it isn’t for now, doesn’t this still give us a path to evolve later?</p><p>Using WebViews effectively can enable teams to move quickly and spend time on what matters most to their customers. Building better Web Apps and WebViews means building better mobile-first experiences that can grow and evolve as you and your company does. Sometimes, web-first is the only solution to getting your product in front of customers and that is fine too. Let’s just make them the best we can! 🥇</p><p><strong>SPECIAL THANKS!<br></strong><em>I want to thank the Chrome teams for all of the effort and great resources that they have built to help us in our journey to higher quality end user experiences from one engineer to another.</em></p><p><em>Editors and commenters:</em> <a href="https://medium.com/u/53310780ca52">Erik Hellman</a>, <a href="https://medium.com/u/6ebfb5fe99f9">Mike Nakhimovich</a>, <a href="https://medium.com/u/de4e4c122516">Mike Wolfson</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=778c7bd709e6" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/the-better-webviews-collection-778c7bd709e6">The Better WebViews Collection</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Android Developer Options that you probably forgot]]></title>
            <link>https://medium.com/@DoesitPew/android-developer-options-that-you-probably-forgot-431e31d4c5fc?source=rss-2ffcf6104135------2</link>
            <guid isPermaLink="false">https://medium.com/p/431e31d4c5fc</guid>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[androiddev]]></category>
            <dc:creator><![CDATA[Stacy Devino]]></dc:creator>
            <pubDate>Wed, 06 Apr 2022 17:23:40 GMT</pubDate>
            <atom:updated>2022-04-06T17:24:49.619Z</atom:updated>
            <content:encoded><![CDATA[<p>Sometimes, I think that we “Old Android Folk” forget to share and re-share the old magic with newer developers or just forget the old ways ourselves.</p><p>Now we are all well past the ANT and Eclipse days, but there are tools that have remained highly functional or grown to be more useful over time. Or, in the light of new tech like ComposeUI, have gained new functionality not easily achievable any way else. 🤫</p><p>Let’s talk about… <a href="https://developer.android.com/studio/debug/dev-options">Developer Options</a></p><p><em>Beware, this article is a re-hash of existing content (see above) in a practical sense for practical devs that actually shows &amp; explains. Not all options will show on all devices.</em></p><p><strong><em>You are now a Developer…</em></strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*qgJhyM-5_9fbLIK5EEl0Hw.gif" /></figure><p>But did you ever take a look at all the tools in there? 👀</p><h3>Standard Debugging Tools</h3><p>Standard things everyone uses almost every day or should know</p><p><strong>Bug report shortcut</strong> — For Bug reports …a shortcut</p><p><strong>Verbose vendor logging</strong> — (Vendor devices only) More complete logging in Logcat than would happen normally and can be useful for vendor interactions, fixing a vendor bug, or debugging an integration (*coughs in Samsung*)</p><p><strong>Enable view attribute inspection</strong> — See View Attributes in the Layout Inspector in Android Studio</p><p><strong>Select debug app + Wait for debugger</strong> — App won’t start unless the debugger is attached in Android Studio, so you can debug things at startup without a crazy logcat to parse and false warnings.</p><p><strong>Verify apps over USB + Verify bytecode of debuggable apps</strong> — Generally smart to make sure you don’t load a dodgy app or general malware by accident (usually online)</p><p><strong>Logger buffer sizes</strong> — Enable longer or shorter logcats (easier to parse longer ones and capture more events)</p><p><strong>Feature Flags</strong> — Enable features normally hidden for most users, usually more useful on pre-release or experimental release builds with features to be enabled (features like PiP and onscreen notifications were originally tested this way)</p><p><strong>Enable GPU debug layers </strong>— Enables Vulkcan validation layers for error checking (Mostly for mobile game developers not using OpenGL, though some made-for-Android libs use it too for 3D rendering)</p><p><strong>Show refresh rate</strong> — It&#39;s the refresh rate of the device and screen currently in use (120FPS!) 😎</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*iyqyrcIOa0W6OdGlBvrKMA.gif" /></figure><h3>Debug and Record Touch Targets and Gestures</h3><p>These two tools are great for recording your work, displaying functionality to others, and debugging things like touch targets and gestures.</p><p><strong>Show taps</strong> — Small round icon onscreen for where the device believes you are touching (or multiples)</p><p><strong>Pointer location</strong> — Shows on screen the whole trace of your finger(s) while in a continuous motion along with coordinates (up top)</p><figure><img alt="Show Taps on screen setting and Show Pointer Location on screen" src="https://cdn-images-1.medium.com/max/800/1*YuuA5MK8FmCG3oeVfibKZQ.gif" /><figcaption>Visible Touch for targets and gestures</figcaption></figure><h3>UI Inspection and Overdraw</h3><p>Set of tools to be used all the time by any app developer with a front end.</p><p><strong>Show surface updates</strong> — Flashes when there are updates to the UI(usually pink)</p><p><strong>Show layout bounds***</strong> — SUPER USEFUL!!! Shows the bounds of each piece as drawn on screen and how they are connected (great for ConstraintLayout), but this has GREAT helpful bits for Compose UI too! For Compose, you can see if you left any remnants or are actually drawing elsewhere then moving. For a11y, you know exactly where your bounds are and how flat your compositions are to better your speed and UI/UX.</p><p><strong>Debug GPU overdraw</strong> — Shows you on-screen where and how you are overdrawing in the space. This is bad for rendering in several ways, but can also indicate why a user interaction isn’t working properly (redraw). Standard color = no overdraw. Blue = 1 layer overdrawn. Green = 2 layer overdraw. Pink = 3 layers overdraw. Red = 4+ layers overdrawn. <em>(This one gets weird with Compose because of how it works with the draw cycles, so take that into account on ComposeUI.)</em></p><p><strong>Show view updates</strong> — Flashes when there are draw updates (multi-color)</p><p><strong>Show hardware layers updates </strong>— Flashes when there are updates to the hardware layers (green)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*wIQGFKRH98-wLYRjQa2f9A.gif" /><figcaption>UI Inspection Tools</figcaption></figure><h3>Visual Performance</h3><p>Know if you are meeting UI performance standards in real-time and what is causing lag.</p><p><strong>Strict mode enabled</strong> — Flashes on screen when something is is holding the main thread too long</p><p><strong>Profile HWUI Rendering</strong> — “On Screen as Bars” presenting a visual of the GPU Rendering graph. Each bar is a “frame” on screen and the height indicates how long it took to render. <strong>The Green Bar represents hitting a 60FPS target which means no visible jitter to the user (16ms), smooth. </strong>The red bar represents 30FPS and beyond it is the “Danger Zone” where it is noticeable by everyone. If the bar fills the screen, the UI skipped those frames as far as the user is concerned. <a href="https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering">Click here to go more in-depth on what each piece means.</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E5f-IDV6gBzmTE1mfduq8Q.gif" /><figcaption>Visual Profiling Performance</figcaption></figure><h3>Testing device crashes and weirdness</h3><p>Emulate systems with low ram or slow processors and certain scenarios that are otherwise hard to replicate. Make your apps resilient and finally track down that one bug! 🐛</p><p><strong>Don’t keep activities </strong>— kill activities as soon as the next activity is started. GREAT for testing back-stack and returning apps from 3rd party integrations where data is lost or computation entirely gone that is needed.</p><p><strong>Background process limit</strong> — Emulate older devices and aggressive runtimes/schedulers that limit what can be run at any given time (can also be a performance boon for older devices). Track down what happens when your async code fails that really should be synchronized tasks.</p><p><strong>Force activities to be resizable </strong>— Know how your app looks and works when resized for different devices like small phones, tables, convertibles and CHROMEBOOKS.</p><p><strong>Force desktop mode</strong> — See how your apps work on desktops like Chromebooks and Tablets when hooked up to external displays like TVs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*Zd4irBObmUOtkF2O8YDSiw.gif" /><figcaption>Test processes along with activity and data loss</figcaption></figure><h3>Recording Traces<em> in the wild! 🏕</em></h3><p>Did you know that you can record a system trace on-device with only the things you care about RIGHT when you happen to see odd behavior, but don’t happen to have your computer handy?</p><p><strong>Record trace</strong> — Starts and ends the system trace</p><p><strong>Trace debuggable applications </strong>— Traces any debuggable applications currently installed</p><p><strong>Categories</strong> — All of the different selectable system processes to trace</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*x6y4Rdwftq-mm7wGZWHDmg.gif" /></figure><h4>Please share any tools that matter to you and message me (<a href="https://twitter.com/DoesitPew">my DMs are open</a>) so that we can continue to add sections depending on what you do as an Android developer (IoT, Game, Flutter, etc). 👋</h4><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=431e31d4c5fc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Drop-in Android Video — Exoplayer2 with Picture in Picture]]></title>
            <link>https://medium.com/s23nyc-tech/drop-in-android-video-exoplayer2-with-picture-in-picture-e2d4f8c1eb30?source=rss-2ffcf6104135------2</link>
            <guid isPermaLink="false">https://medium.com/p/e2d4f8c1eb30</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[nike]]></category>
            <category><![CDATA[exoplayer2]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Stacy Devino]]></dc:creator>
            <pubDate>Thu, 13 Sep 2018 15:58:58 GMT</pubDate>
            <atom:updated>2018-09-13T15:58:58.032Z</atom:updated>
            <content:encoded><![CDATA[<p>Beautiful, interactive video content done easy.</p><p>Video has been shown to increase engagement in apps and with content. <em>How else could gif servicing apps be worth tens of millions and Youtube influencers be selling billions of dollars of product?</em></p><p>The problem is that video in Android can be difficult. You are given the assumption that the video types you will support are pretty basic : embedded MPEG video, URLs to MP4/AVI files, and HLS Streaming. A naive approach would be to use the embedded video player sent with Intents, but that takes the user out of your app. You think, 🤔 “ I’ll just use the Android VideoView class with AppCompat .” Then you realize that it is nearly impossible to make the experience consistent across users because of codec support and whatever the phone manufacturer may have customized in the video player. Finally you settle on <a href="https://github.com/google/ExoPlayer">Exoplayer2</a> because it is the package everyone, including Google, uses. Upon further inspection, you quickly realize that it requires a lot of code to make it work and the <a href="https://github.com/google/ExoPlayer/tree/release-v2/demos/main/src/main">example application on the Github</a> is confusing, far too extensive for what you are doing. Another route you could take is <a href="https://github.com/brianwernick/ExoMedia">ExoMedia by Brian Wernick</a>, but it trades extensibility, theming, and control for simplicity.</p><p>Fortunately, you only need the Exoplayer2 libraries to have a fantastic video player experience within your app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*sKdgN6tr11zt6qQnHzbFpw.gif" /></figure><h4>SimpleExoplayer “2” the Rescue</h4><p>First you need to make an app which can receive a URL and play it using Exoplayer. For this, you will need <a href="https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/SimpleExoPlayer.html">SimpleExoplayer</a> and the Playerview from the UI package. Playerview has easy to manipulate styling/theming elements as well as available media controls. SimpleExoPlayer gives you a preconfigured player to work with without having to handle deeper logic yourself.</p><p>Start by adding the Gradle dependencies to your build.gradle :</p><pre>implementation &#39;com.google.android.exoplayer:exoplayer-core:2.X.X&#39;<br>implementation &#39;com.google.android.exoplayer:exoplayer-hls:2.X.X&#39;<br>implementation &#39;com.google.android.exoplayer:exoplayer-ui:2.X.X&#39;<br>implementation &#39;com.google.android.exoplayer:extension-mediasession:2.X.X&#39;</pre><p>Prepare a layout for your video:</p><pre><em>&lt;?</em>xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;<em>?&gt;<br></em>&lt;android.support.constraint.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;<br>    android:layout_width=&quot;match_parent&quot;<br>    android:layout_height=&quot;match_parent&quot;<br>    android:background=&quot;@android:color/black&quot;&gt;<br><br>    &lt;com.google.android.exoplayer2.ui.PlayerView<br>        android:id=&quot;@+id/playerView&quot;<br>        android:layout_width=&quot;match_parent&quot;<br>        android:layout_height=&quot;match_parent&quot;<br>        android:layout_gravity=&quot;center&quot; /&gt;<br><br>&lt;/android.support.constraint.ConstraintLayout&gt;</pre><p>Implement Exoplayer in a VideoActivity (Kotlin) :</p><p><em>Quick Note</em> : Exoplayer should be set up in the onStart() for quick initialization that does not interfere with onCreate().</p><pre>lateinit var player : SimpleExoPlayer<br>var mUrl = *some string HLS/Standard video url passed to it*</pre><pre>override fun onStart() {<br>    super.onStart()<br><br><br>    player = ExoPlayerFactory<br>             .newSimpleInstance(this, DefaultTrackSelector())<br><br>    playerView.player = player<br><br>    val dataSourceFactory = <br>                DefaultDataSourceFactory(this, <br>                Util.getUserAgent(this,                  <br>                applicationInfo.loadLabel(packageManager)<br>                .toString()))</pre><pre><br>    when (Util.inferContentType(Uri.parse(mUrl))) {<br>        C.TYPE_HLS -&gt; {<br>            val mediaSource = HlsMediaSource<br>                .Factory(dataSourceFactory)<br>                .createMediaSource(Uri.parse(mUrl))<br>            player.prepare(mediaSource)<br>        }<br><br>        C.TYPE_OTHER -&gt; {<br>            val mediaSource = ExtractorMediaSource<br>                .Factory(dataSourceFactory)<br>                .createMediaSource(Uri.parse(mUrl))<br>            player.prepare(mediaSource)<br>        }<br><br>        else -&gt; {<br>            //This is to catch SmoothStreaming and<br>            //DASH types which we won&#39;t support currently, exit<br>            finish()<br>        }<br>    } <br>    player.playWhenReady = true<br>}</pre><pre>override fun onStop() {<br>    super.onStop()<br>    playerView.player = null<br>    player.release()<br>}</pre><p>That was almost <em>too easy</em>!</p><h4>Boost it with Picture-in-Picture</h4><p>This is where things get interesting.</p><p>According to the basic documentation on<a href="https://developer.android.com/guide/topics/ui/picture-in-picture"> AndroidDevelopers</a>, this is all you need to get started with <em>Picture in Picture :</em></p><pre>//For N devices that support it, not &quot;officially&quot;<br>@Suppress(&quot;DEPRECATION&quot;)<br>fun enterPIPMode(){<br>    if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.N<br>            &amp;&amp; packageManager<br>            .hasSystemFeature(<br>            PackageManager.FEATURE_PICTURE_IN_PICTURE)) {<br>        videoPosition = player.currentPosition<br>        playerView.useController = false<br>        if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.O) {<br>            val params = PictureInPictureParams.Builder()<br>            this.enterPictureInPictureMode(params.build())<br>        } else {<br>            this.enterPictureInPictureMode()<br>        }<br>}</pre><p>Now, I will say that while implementing <em>Picture in Picture</em> support, Google was notified of issues in documentation. It’s better now and includes some of the things like checking if the system has the feature (some devices will not have <em>Picture in Picture</em> because of RAM considerations) which is shown above. 👏</p><p>Next, hook up switching to <em>Picture in Picture</em> when the user hits the Recents, Home, and Back buttons here :</p><pre>override fun onBackPressed(){<br>    if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.N<br>            &amp;&amp; packageManager<br>            .hasSystemFeature(<br>            PackageManager.FEATURE_PICTURE_IN_PICTURE)){<br>        enterPIPMode()<br>    } else {<br>        super.onBackPressed()<br>    }<br>}</pre><pre>/Called when the user touches the Home or Recents button to leave the app.<br>override fun onUserLeaveHint() {<br>    super.onUserLeaveHint()<br>    enterPIPMode()<br>}</pre><p>Google has added some of this to the documentation recently 😀, but not onBackPressed() 🧐 . <em>What could be unique about </em><em>onBackPressed()?</em></p><p><strong><em>Permission Handling</em></strong></p><p>The user can turn off this permission as part of the <strong>Advanced </strong>permissions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*Kn-pJ9poGyOi9sS7nLLZQg.gif" /></figure><p>If you are familiar with deeper permission handling as well as the system the normal <em>Permissions </em>tie into, you should just be able to handle this case in <strong>AppOpsManager</strong>. In that case, the code would look like this :</p><pre>val appOpsManager = <br>    getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager</pre><pre>if(appOpsManager.checkOpNoThrow(<br>    AppOpManager.OP_PICTURE_IN_PICTURE, <br>    packageManager.getApplicationInfo(packageName, <br>    PackageManager.GET_META_DATA).uid, packageName)<br>    == AppOpsManager.MODE_ALLOWED){<br>     //Picture in Picture is enabled, yay!<br>}</pre><p>Oh, there is a problem with that. It’s not valid in Android Studio 😠.</p><pre><a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/studio-3.1.2/core/java/android/app/AppOpsManager.java#L1640">https://github.com/aosp-mirror/platform_frameworks_base/blob/studio-3.1.2/core/java/android/app/AppOpsManager.java#L1640</a></pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IuW87wNUIlWCuuEWUrnvyw.png" /></figure><p>For the Integer-Op-type, <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/studio-3.1.2/core/java/android/app/AppOpsManager.java#L1511">unlike the String-Op-type version</a>, the ability to check the permission is hidden 🙅.</p><p><strong><em>WHY?!?!</em></strong></p><p>One likely answer is that this was a carry-over from N where Google didn’t anticipate <em>Picture in Picture</em> becoming a public feature or <em>Google could be trying to enforce certain developer behavior.</em> Right now, if the user does not have the <em>Picture in Picture</em> mode enabled in these special permissions, it will not work and there isn’t a nice way to check it 😥. For apps, it means that you cannot enable <em>Picture in Picture</em> mode for onBackPressed() without risking needless crashes and an unpleasant user experience.</p><p><em>Pressing the back button seems like the most common use-case for small video content, so this lack of handling the </em><em>Back button doesn’t entirely make sense.</em></p><p>Again, they could have this fixed in the proper AndroidStudio 3.2.X release, but that isn’t out yet.</p><p>So, you need to be a little creative here 🤔 :</p><pre>fun enterPIPMode(){<br>        if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.N<br>            &amp;&amp; packageManager<br>            .hasSystemFeature(<br>            PackageManager.FEATURE_PICTURE_IN_PICTURE)) {</pre><pre>         ...<br>         ...</pre><pre>    Handler().postDelayed(<strong>{</strong>checkPIPPermission()<strong>}</strong>, 30)<br>    }<br>}</pre><pre>@RequiresApi(Build.VERSION_CODES.N)<br>fun checkPIPPermission(){<br>    isPIPModeeEnabled = isInPictureInPictureMode<br>    if(!isInPictureInPictureMode){<br>        onBackPressed()<br>    }<br>}</pre><p>I know, it seems a bit <em>jank</em>. But, 25–30ms to check whether the Activity actually went into <em>Picture in Picture</em> mode is plenty of time, even devices that have <em>very limited memory (512mb + running other apps),</em> to enter <em>Picture in Picture</em> mode as far as the system is concerned. It also gives you enough time to send the onBackPressed() so that the system knows you aren’t holding the user hostage and the delay is neigh imperceptible by the user. <em>Yes, this is under the AOSP “Bad Behavior” timer. </em>😜</p><h4><strong>More </strong>Picture in Picture<strong> oddness discovered</strong></h4><p>Now that <em>Picture in Picture</em> mode is working and no crashes can really happen, there is another scenario which comes up.</p><p>The user hits the “X” or drags the <em>Picture in Picture</em> window down to dismiss. You would expect that this simply calls finish() and the window disappears into the ether. But, it doesn’t. You can still see the activity lingering in the Recents for what seems like <em>forever as you wait for it to be collected………*3 minutes later*………….now. </em>😞</p><p>This is a problem because:</p><ol><li>It doesn’t <em>look</em> good for your app</li><li>It can be a major headache if you want to start up a new <em>Picture in Picture</em> instance because the old one is just lingering there.</li></ol><p>If you watch to see what is getting called when the user “closes” a <em>Picture in Picture</em> window you see this being called :</p><pre>override fun onStop() {<br>    super.onStop()<br>}</pre><p>Logically, finish() is being called and that is one of the first LifecycleEvents part of that flow. But, not in the traditional sense.</p><p><strong>Picture-in-Picture mode is unique, especially in the separate Activity case, because it acts like it is <em>entirely its own app</em>.</strong></p><p>To get around this problem and clear out the VideoActivity quickly and efficiently implement this :</p><pre>override fun onStop() {<br>    ....<br>    ....<br>    if ((Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.N)<br>        &amp;&amp; packageManager.hasSystemFeature(<br>        PackageManager.FEATURE_PICTURE_IN_PICTURE)) {<br>        finishAndRemoveTask()<br>    }<br>}</pre><p>Done. Killed and removed appropriately.</p><h4>Adding Media Controls to Picture-in-Picture</h4><p>Start/Stop/Next</p><p>By default, Exoplayer + <em>Picture in Picture </em>does not give you the <em>Picture in Picture</em> media controls you see in the Youtube player for instance. <em>Picture in Picture</em> mode depends on a MediaSession to handle that communication. If you have written an AndroidTV application, audioplayer, or video-centric app before, you should be familiar with it. It can also be used for handling media controls from outside devices (like bluetooth headsets), widgets or advanced notifications with media controls. Here, you will be using the <a href="https://github.com/google/ExoPlayer/tree/release-v2/extensions/mediasession"><strong>MediaSession Extension Library</strong></a> to take care of this. ⏯</p><p>Just add this code in the onStart() after handling your Exoplayer :</p><pre>val mediaSession = MediaSessionCompat(this, packageName)<br>val mediaSessionConnector = MediaSessionConnector(mediaSession)<br>mediaSessionConnector.setPlayer(player, null)<br>mediaSession.isActive = true</pre><h4>One More Thing</h4><p>Since <em>Picture in Picture</em> essentially acts like its own Application, <em>it can live on without the Main App</em>.</p><p>Yes, if you kill your Main App the <em>Picture in Picture</em> window will live on without you unless you <em>kill it first. </em>That means, if you have connecting information (such as multiple URLs or other streaming info) being sent and resetting the content of the <em>Picture in Picture</em> window that connection will be lost and it will become a <strong><em>ZOMBIE Picture in Picture WINDOW</em></strong> 😱.</p><p>What you can do to solve this is register an “Exit Event” Service to capture when the user <em>Force Closes</em> your app. In this Service, you need to override onTaskRemoved(rootIntent:Intent) and send a signal (RxJava Observable/Subscriber, Eventbus, Intent, etc.) which the <em>Picture in Picture</em>’d Activity can catch and call finishAndRemoveTask() to make a <em>clean exit.</em></p><h4>Code</h4><p>There you have it folks! Lots of little gotchas taken care of.</p><p>Full code example with a <strong>DROP-IN </strong>VideoActivity.kt you can use right now.</p><h3><a href="http://bit.ly/exoplayerPIPexample"><strong>http://bit.ly/exoplayerPIPexample</strong></a></h3><p>It includes some extra, useful features:</p><ul><li>Play/Pause/Resume/SeekTo controls for v ideo</li><li>Show/Hide Exoplayer controls when the user goes in-out of <em>Picture in Picture</em> mode</li><li>Rotation handling (fullscreen and vertical with no placement loss).</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FpkarE-BZE3A%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DpkarE-BZE3A&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FpkarE-BZE3A%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/fb737f52319a9fc2d948fa8d478eeca1/href">https://medium.com/media/fb737f52319a9fc2d948fa8d478eeca1/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e2d4f8c1eb30" width="1" height="1" alt=""><hr><p><a href="https://medium.com/s23nyc-tech/drop-in-android-video-exoplayer2-with-picture-in-picture-e2d4f8c1eb30">Drop-in Android Video — Exoplayer2 with Picture in Picture</a> was originally published in <a href="https://medium.com/s23nyc-tech">S23NYC: Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>