<?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[S23NYC: Engineering - Medium]]></title>
        <description><![CDATA[S23NYC is Nike&#39;s first digital experience studio and they&#39;ve been the driving force behind some of the brand&#39;s most memorable moments. - Medium]]></description>
        <link>https://medium.com/s23nyc-tech?source=rss----f165243cf853---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>S23NYC: Engineering - Medium</title>
            <link>https://medium.com/s23nyc-tech?source=rss----f165243cf853---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 23 May 2026 15:49:37 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/s23nyc-tech" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Using Machine Learning and CoreML to control ARKit]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/s23nyc-tech/using-machine-learning-and-coreml-to-control-arkit-24241c894e3b?source=rss----f165243cf853---4"><img src="https://cdn-images-1.medium.com/max/600/1*fS9uwvilbitcm5MvMRAcQA.gif" width="600"></a></p><p class="medium-feed-snippet">Combining image classification and augmented reality to create new experiences</p><p class="medium-feed-link"><a href="https://medium.com/s23nyc-tech/using-machine-learning-and-coreml-to-control-arkit-24241c894e3b?source=rss----f165243cf853---4">Continue reading on S23NYC: Engineering »</a></p></div>]]></description>
            <link>https://medium.com/s23nyc-tech/using-machine-learning-and-coreml-to-control-arkit-24241c894e3b?source=rss----f165243cf853---4</link>
            <guid isPermaLink="false">https://medium.com/p/24241c894e3b</guid>
            <category><![CDATA[augmented-reality]]></category>
            <category><![CDATA[coreml]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[engineering]]></category>
            <dc:creator><![CDATA[Dan Wyszynski]]></dc:creator>
            <pubDate>Wed, 09 Jan 2019 21:59:38 GMT</pubDate>
            <atom:updated>2019-01-13T03:19:16.429Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Geometric Android Animations using the Canvas]]></title>
            <link>https://medium.com/s23nyc-tech/geometric-android-animations-using-the-canvas-dd687c43f3f4?source=rss----f165243cf853---4</link>
            <guid isPermaLink="false">https://medium.com/p/dd687c43f3f4</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[snkr]]></category>
            <category><![CDATA[animation]]></category>
            <category><![CDATA[nike]]></category>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[Alexio Mota]]></dc:creator>
            <pubDate>Thu, 13 Dec 2018 20:27:38 GMT</pubDate>
            <atom:updated>2018-12-13T20:27:37.861Z</atom:updated>
            <content:encoded><![CDATA[<p>Our team at s23NYC recently had the pleasure of bringing the SNKRS Pass experience to the Android app. SNKRS Pass is a feature in the SNKRS app that allows users to reserve the hottest kicks for pickup in brick-and-mortar retail stores. After a user has reserved their <em>soon-to-be favorite</em> shoe, the SNKRS Pass is their premium digital voucher that unlocks in-store pickup.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*UYvRUQ8rHNo_AkiwmRAf0w.gif" /></figure><p><a href="https://medium.com/s23nyc-tech/prototyping-animations-in-swift-97a9cfb1f41b">Previously, our team published a post how this was accomplished for the iOS SNKRS Pass experience</a>. Today, you will learn how our team accomplished this for Android. This post focuses on the wave animation portion of the feature.</p><p>Our first attempt at the animation was a <a href="https://airbnb.io/lottie/">Lottie</a> Drawable generated by our design team and it worked marvelously. <em>However, we ran into issues.</em> We couldn’t scale it properly to center the animation on the spinning circle view origin without making the animation view larger than the display size or leaving an awkward space at the bottom of the screen. Ideally, we’d want to set the wave origin at runtime. Since we were unable to go with the “easy” solution of using the Lottie asset, we instead set out to create the animation manually. We try to use Lottie for most of our animations, but it didn’t fit our case of a fullscreen animation being centered on a separate view.</p><p>After an introductory attempt by our team, we were fortunate enough to have the always incredible <a href="https://medium.com/u/22c02a30ae04">Nick Butcher</a> point us in the right direction with a proof of concept of what we were trying to achieve. Below is our implementation which heavily leaned on Nick’s proof of concept.</p><h3>Creating the wave animation</h3><p>Let’s create a similar UI to the animated wave and circle view in SNKRS Pass. First, you’ll start by drawing circles and animating them out from the center of the screen.</p><p>You’ll define attributes for the space between each rendered circle and the circle color and stroke width:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f998a66350a8482a47c302ee0dcfa97b/href">https://medium.com/media/f998a66350a8482a47c302ee0dcfa97b/href</a></iframe><p>Next, you will need to define a layout and custom view that shows a number of concentric circles.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ddfeae0db5928debafdd96aa86b00cc4/href">https://medium.com/media/ddfeae0db5928debafdd96aa86b00cc4/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6f13f91d93c864a5c02a338ce4e36030/href">https://medium.com/media/6f13f91d93c864a5c02a338ce4e36030/href</a></iframe><p>In this view:</p><ol><li>Initialize the paint object with the custom attributes</li><li>Define the initial and max radius of the smallest/largest circle</li><li>Define the position where to start drawing the circles</li><li>Draw all the circles with a radius of initialRadius to maxRadius with each separated by a space of waveGap</li></ol><p>Now, you should see a static representation of circles that flood our screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/263/1*QL3SbVbVvh87se7oBgQcIw.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/287e9f71fef2ff3edd1159e1b8427f99/href">https://medium.com/media/287e9f71fef2ff3edd1159e1b8427f99/href</a></iframe><p>This is done by creating a value animator that runs for 1.5 seconds, repeating in an endless loop. On every animation frame, the waveRadiusOffset will be updated — waveRadiusOffset is the value that tracks the circle expansion from its original position. Next we call postInvalidateOnAnimation() in the setter to redraw our view’s next frame. Finally, onDraw runs using the new offset in order to simulate the animation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/310/1*lflpcJiE9pSKRasRWFM-ng.gif" /></figure><p><em>Reminds me of the Twilight Zone’s intro.</em></p><h3>More than a Circle</h3><p>Circles are fine, but the animation needs a defined shape. Taking a cue from the iOS article from our team, here you will be making a 10-point star.</p><p>We’ll use simple trigonometry (still remember sohcahtoa?) to draw our star. For reference and if you need a refresher: <a href="https://en.wikipedia.org/wiki/Trigonometry#Mnemonics">https://en.wikipedia.org/wiki/Trigonometry#Mnemonics</a></p><p>Creating the star path line by line looks like so:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/176/1*cJqZ1N2D4znyKR7_D71jQA.gif" /><figcaption>Canvas animation of the lines being added to the path one by one</figcaption></figure><p>First you need to figure out where each point will lie. Since a full rotation is 360 degrees (2π in radians), we just need to divide that by the number of points to know the angle at which each point will sit. Knowing the angle and the diagonal distance each point is from the center (also known as the radius/hypotenuse), we can use <strong>sin() </strong>and <strong>cos()</strong> to find the opposite (or y) and adjacent (or x) distances to plot each line between points.</p><pre>sin(angle) = opposite/hypotenuse    cos(angle) = adjacent/hypotenuse</pre><pre><strong>rearrange to find unknown:</strong></pre><pre>opposite = hypotenuse*sin(angle)   adjacent = hypotenuse*cos(angle)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/176/1*X0uuwWARo454y9IAuKwFLw.gif" /><figcaption>Canvas animation with angles at which lines are added</figcaption></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7ed0d36018f6e74d6e5a8df922252ff0/href">https://medium.com/media/7ed0d36018f6e74d6e5a8df922252ff0/href</a></iframe><p>If we put it all together and draw this star instead of the circle we will end up with this new wave animation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/240/1*uLbA-NYIDV2QLos6qkb4EA.gif" /><figcaption>Waves with star path</figcaption></figure><p>Changes made to onDraw:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/824f3c8babcc02fd2695bf26a3be6688/href">https://medium.com/media/824f3c8babcc02fd2695bf26a3be6688/href</a></iframe><h4>The Gradient</h4><p>The SNKRS Pass voucher has a gradient that moves as you tilt your phone. It applies to the waves but not the background.</p><p>To highlight the wave portions that fall within the gradient, we can use PorterDuff.Mode.SRC_IN to color the parts of the canvas that have already been touched. (<a href="https://developer.android.com/reference/android/graphics/PorterDuff.Mode">more information here</a>)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a7fdc364c916a499cba10db2db43f8cb/href">https://medium.com/media/a7fdc364c916a499cba10db2db43f8cb/href</a></iframe><p>Next we need to create and draw our gradient using different green alphas.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b31a27cd9ef210a436feb2d80bb38942/href">https://medium.com/media/b31a27cd9ef210a436feb2d80bb38942/href</a></iframe><p>Finally, we need to update our view to use the software layer since PorterDuff.Mode.SRC_IN doesn’t work with hardware acceleration.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6a4fb39f81cb9409bd6f49bd6be80110/href">https://medium.com/media/6a4fb39f81cb9409bd6f49bd6be80110/href</a></iframe><p>And, voila:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/258/1*d0h1Jv9qVTahI16uR9zB1Q.gif" /></figure><h4>Motion</h4><p>To move the gradient on the canvas, we’ll translate the gradient paint shader’s local matrix by a calculated amount.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/349731379091a65b0bb1922b1f1894d5/href">https://medium.com/media/349731379091a65b0bb1922b1f1894d5/href</a></iframe><p>Now we’ll use the acceleration and magnetic sensor to get the phone’s orientation and determine how much to translate the gradient as we tilt the phone.</p><p>Further sensor reading: <a href="https://developer.android.com/guide/topics/sensors/sensors_position">https://developer.android.com/guide/topics/sensors/sensors_position</a></p><p>Let’s create a class WaveTiltSensor that will hold the logic for getting the device’s orientation angles. You’ll define an interface for the tilt sensor along with an associated listener and the initialization logic for the accelerometer and magnetic sensors:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5f4a86ff5687880e902881357dc59640/href">https://medium.com/media/5f4a86ff5687880e902881357dc59640/href</a></iframe><p>Next, you will need to implement the tilt detection logic in onSensorChanged. The sensor data you need is the angle the phone is on the x and y axis as it rotates.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d4b7f0a7962c47ffe2fa01094cf99cab/href">https://medium.com/media/d4b7f0a7962c47ffe2fa01094cf99cab/href</a></iframe><p>Now, you have the pitch (<em>degrees of rotation about the x axis</em>) and roll (<em>degrees of rotation about the y axis</em>) in radians. Since you’re translating the phone’s rotation to a 2D motion, the azimuth (<em>degrees of rotation about the -z axis</em>) doesn’t matter.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/385/1*KU3cwSgU5MWmNYCr7c3F2Q.png" /><figcaption>Visual representation of device orientation with respect to pitch, roll, etc</figcaption></figure><p>When your device’s pitch changes, you want to move the gradient up and down the y-axis. When the roll changes, the gradient should move left/right on the x-axis. The center of the gradient shouldn’t move past the edges of the screen so you need to constrain movement by half the height/width since it’s centered.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/446/1*umVfRDmQZWmwCZ11m8OQPg.png" /><figcaption>Illustration of pitch; roll is the same concept but with side tilts.</figcaption></figure><p>Let’s change the view to handle device tilt updates. We’ll have it implement the TiltListener interface.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b6c523f94ec8a876e24de74ac95327d3/href">https://medium.com/media/b6c523f94ec8a876e24de74ac95327d3/href</a></iframe><p>As we get tilt events, the code performs a bit of trigonometry to get the adjacent vertical offset distance that the device is from its resting state for both the pitch and roll. In this example, the device is starting from a flat resting point on a table. Then, the code will translate the gradient by no more than half the screen height/width.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fplayer.vimeo.com%2Fvideo%2F305999519%3Fapp_id%3D122963&amp;dntp=1&amp;url=https%3A%2F%2Fvimeo.com%2F305999519&amp;image=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F745636607_960.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=vimeo" width="1080" height="1920" frameborder="0" scrolling="no"><a href="https://medium.com/media/b5e08cd563c79db989815cc0f2f2ec0a/href">https://medium.com/media/b5e08cd563c79db989815cc0f2f2ec0a/href</a></iframe><p>I updated the background color to give a little more contrast to the green gradient in the video.</p><h4>Conclusion</h4><p>Attached you’ll find an example Github project that you can play around with. I hope this post has made doing trigonometric and gyro based animations easier for you!</p><p><a href="https://github.com/alexio/Android-AnimatedWaveView">alexio/Android-AnimatedWaveView</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dd687c43f3f4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/s23nyc-tech/geometric-android-animations-using-the-canvas-dd687c43f3f4">Geometric Android Animations using the Canvas</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>
        <item>
            <title><![CDATA[AWS Re:Invent 2018 — Serverless Shock Drops]]></title>
            <link>https://medium.com/s23nyc-tech/aws-re-invent-2018-serverless-shock-drops-664a2de2cb7b?source=rss----f165243cf853---4</link>
            <guid isPermaLink="false">https://medium.com/p/664a2de2cb7b</guid>
            <category><![CDATA[dynamodb]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[lambda-layer]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[Chris Edwards]]></dc:creator>
            <pubDate>Mon, 10 Dec 2018 15:42:05 GMT</pubDate>
            <atom:updated>2018-12-10T15:42:05.767Z</atom:updated>
            <content:encoded><![CDATA[<h3>AWS Re:Invent 2018 — Serverless Shock Drops</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*clLnki-cBoIwzVDeeJsXIg.jpeg" /></figure><p>Last week our API Services team attended the AWS Re:Invent Conference in Las Vegas. In between trips to the El Cortez Casino and the Bacchanal Buffet, it was quite the week.</p><p>Here at S23NYC we rely on AWS Serverless technologies — mostly Lambda and DynamoDB — to support the Nike SNKRS App and its admin tools. DynamoDB and Lambda definitely have their strongpoints: automatic scaling, thorough documentation, concise, easy to maintain business logic and fast iterative development and deployment. And best of all, they’re fully managed, allowing our lean team to turn out APIs effectively and with lightning fast turnaround. While no platform is without its quirks or drawbacks, a handful of announcements last week whittled away quite a few big ones.</p><p><strong>API Gateway Web Sockets<br></strong>This one’s a doozy. Because most of our APIs run on Lambda behind a VPC, other than running a socket server on an EC2 Cluster (no thanks) or using a third party service like <a href="https://www.pubnub.com">Pubnub</a>, there was no obvious solution to real-time client-server communication.</p><p>But now with the announcement of web socket support for API Gateway, deploying our own, fully managed socket service is easier than ever. API Gateway sends and accepts socket requests to and from the client, invoking stateless lambda functions as needed while connection information can be persisted through DynamoDB. For a deeper dive on this, there’s a <a href="https://serverless.com/blog/api-gateway-websockets-support/">great write-up on a simple chat client implementation over at serverless.com</a>.</p><p><strong>Lambda Layers and Custom Runtimes<br></strong>We love Python! But hey, maybe it’s not your language of choice. Maybe you prefer a functional language like Haskell or you’re ride-or-die PHP. Maybe you’d prefer a compiled language like C# or Java that isn’t C# or Java. Well, in addition to newly announced supported runtimes for <a href="https://aws.amazon.com/blogs/opensource/rust-runtime-for-aws-lambda/">Rust</a>, <a href="https://aws.amazon.com/blogs/compute/introducing-the-c-lambda-runtime/">C++</a>, and <a href="https://aws.amazon.com/blogs/compute/announcing-ruby-support-for-aws-lambda/">Ruby</a>, AWS now allows <a href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html">custom runtimes for Lambda</a>. If it runs on Unix, you can run it on Lambda.</p><p>Of course, simplicity and ease of deployment are big advantages with using Lambda so it’s recommended that you use the prepackaged runtimes provided by AWS whenever possible. The core component that enables these custom runtimes is the ability to share code across multiple functions with <a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html">Lambda Layers</a>. This can be leveraged for much more practical outcomes, such as speeding up your CI builds, shrinking the size of your deployment packages, or potentially reducing your function’s cold start times.</p><p>Say for instance you have a shared API wrapper for an often used third-party service across your various Lambda functions. You can create a Lambda Layer containing these packages and modules and configure your function to reference it by <a href="https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html">ARN</a>. We ran a test with one of our services and the size of our deployment ZIP dropped from 28MB to 9K. Layers can be isolated to a single AWS account or publicly available to other AWS developers. Some companies, <a href="https://www.datadoghq.com/blog/datadog-lambda-layer/">such as Datadog</a>, have already announced their own publicly available Lambda Layers.</p><p>As with any system of dependency management, there are a few gotchas when it comes to using Lambda Layers. Layers are immutable, meaning in order to update a layer’s shared code a Lambda’s configuration needs to be updated to point at a new ARN. When deleting a Layer or revoking its use permissions, functions that were using that layer will still run, but you won’t be able to create new functions that use the deleted layer. It’s also important to note that the overall size of the function code, its custom runtime and any configured layers must comply with the <a href="https://docs.aws.amazon.com/lambda/latest/dg/limits.html">usual deployment package size limit</a>.</p><p><strong>DynamoDB On Demand Capacity<br></strong>A major challenge we face working on SNKRS is intense bursts of API traffic, usually the result of a high heat product launch with a feature like SNKRS Pass. Without allocating a prohibitively expensive base capacity, this has required logging in to AWS and manually raising the read and write allocations to prevent throttling and service timeouts. Not a huge deal if a drop falls in the middle of the day here on the East Coast, but when supporting launches in China or Japan it can be quite the bummer.</p><p>Now with <a href="https://aws.amazon.com/blogs/aws/amazon-dynamodb-on-demand-no-capacity-planning-and-pay-per-request-pricing/">DynamoDB On Demand</a>, instead of specifying a maximum RCU and WCU and enabling <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-partition-key-design.html#bp-partition-key-partitions-adaptive">adaptive capacity</a> (which can take a few minutes to kick in, leading to temporary throttling) <a href="https://aws.amazon.com/dynamodb/pricing/on-demand/">you can opt to pay per request</a>. Just make sure you know what you’re getting into, as this bypasses any estimated cost predictions.</p><p><strong>DynamoDB ACID Transactions</strong><br>Speaking of DynamoDB, on the last day of the conference I ran into my Uncle Bob on my way to the casino. I had no idea that he was in town, and to be honest, I didn’t think he’d be be interested in NoSQL—he’s a software engineer for the FBI and I just assumed they use Oracle—but one thing we were both super excited about was the announcement of <a href="https://aws.amazon.com/blogs/aws/new-amazon-dynamodb-transactions/">DynamoDB Transactions</a>.</p><p>What does this mean? Prior to this announcement, DynamoDB met two of the four requirements of <a href="https://en.wikipedia.org/wiki/ACID_(computer_science)">ACID</a>: Consistency and Durability. Thanks to <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html">ConditionExpressions</a>, Atomicity and Isolation applied, but only pertaining to updates to a single item or row. Now with transactions, we can specify conditional reads or writes across not only multiple rows of a table, but multiple rows in multiple tables.</p><p>This means we can now leverage one of the key advantages of relational databases without any of the downside like unmanaged concurrency or deadlocks. Yes, <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html">the recommended use cases for DynamoDB encourage single table designs</a>, a key tenant of non-relational document-store patterns, but situations can arise over the course of an application’s lifetime where conditionally manipulating data across multiple tables becomes useful or even necessary.</p><p><strong>Recommended Viewing<br></strong>These are my 3 favorite talks from last week. For a deeper dive on any of these subjects check out the following:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FTR9LY_e8Y1s%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DTR9LY_e8Y1s&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FTR9LY_e8Y1s%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/072926dd95a5004fcdec72f092d2d316/href">https://medium.com/media/072926dd95a5004fcdec72f092d2d316/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FQdzV04T_kec%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DQdzV04T_kec&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FQdzV04T_kec%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/4a02747b83a7fc5df2d1b78b24d900dd/href">https://medium.com/media/4a02747b83a7fc5df2d1b78b24d900dd/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FHaEPXoXVf2k%3Fstart%3D2289%26feature%3Doembed%26start%3D2289&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DHaEPXoXVf2k&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FHaEPXoXVf2k%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/b845f491ce1c784921c31f68e37af92b/href">https://medium.com/media/b845f491ce1c784921c31f68e37af92b/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=664a2de2cb7b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/s23nyc-tech/aws-re-invent-2018-serverless-shock-drops-664a2de2cb7b">AWS Re:Invent 2018 — Serverless Shock Drops</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>
        <item>
            <title><![CDATA[Room with a View]]></title>
            <link>https://medium.com/s23nyc-tech/room-with-a-view-8cc759b312cb?source=rss----f165243cf853---4</link>
            <guid isPermaLink="false">https://medium.com/p/8cc759b312cb</guid>
            <category><![CDATA[database]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[gde]]></category>
            <dc:creator><![CDATA[Mike Nakhimovich]]></dc:creator>
            <pubDate>Fri, 09 Nov 2018 18:57:23 GMT</pubDate>
            <atom:updated>2018-11-09T18:57:22.606Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_7g2yTqIWDE0QiARZb9DSQ.jpeg" /><figcaption>View from the S23NYC office</figcaption></figure><p>Recently, our Android team started going through tech debt and spiking on how we can continue to have a best in class SNKRS experience for our users. While the app is beloved, as evident by our 4.5 rating on Play Store, we still felt that it was necessary to update our architecture to the latest and greatest that Android has to offer. In this post I’ll be going over the work we are doing to take the old persistence model, which stored flattened feed objects into Sqlite directly, and convert it to a relation schema using Google’s fantastic <a href="https://developer.android.com/topic/libraries/architecture/room">Room</a> ORM. I’ll be going over the good, the bad, and the ugly of using Room.</p><h3>The Good</h3><h4>Kotlin Support</h4><p>Room works with Kotlin out of the box, you never have to write a line of java to work with Room. Your data models, DAOs, &amp; database can all be written in Kotlin.</p><h4>API Done Right</h4><p>It takes all of ten minutes to be up and running with Room:</p><p>First, create Kotlin data class and annotate them as room entities</p><pre>@Entity<br>data class Sneaker(<br>        @PrimaryKey val id: String,<br>        ...<br>)</pre><p>Next, create a BaseDAO (Thanks <a href="https://medium.com/androiddevelopers/7-pro-tips-for-room-fbadea4bfbd1">Florina for the tip!</a>)</p><pre>interface BaseDao&lt;T&gt; {<br>    @Insert(onConflict = OnConflictStrategy.<em>REPLACE</em>)<br>    fun insert(vararg obj: T)<br><br>    @Delete<br>    fun delete(vararg obj: T)<br>}</pre><p>Now, you can create abstract DAOs like the one below. You will only have to define query functions. Insert/Delete functionality will be inherited from your base DAO. Overall, find the syntax to feel familiar for those that use retrofit</p><pre>@Dao<br>abstract class SneakerDAO : BaseDao&lt;Sneaker&gt; {<br>    @Query(&quot;Select * from Sneaker where id like :id&quot;)<br>    abstract fun getSneakerById(id: String): Sneaker<br>}</pre><p>Once you have your DAOs, you create an abstract class with your DAOs and entities:</p><pre>@Database(<br>        entities = [Sneaker::class, ...],<br>        version = 1)<br>abstract class SnkrsDB : RoomDatabase() {<br>    abstract fun sneakerDAO(): SneakerDAO<br>    ...<br>}</pre><p>Finally, you create an instance of your DB:</p><pre>@Singleton fun provideRoomDB(context: Context): SnkrsDB =<br> Room.databaseBuilder(context, SnkrsDB::class.<em>java</em>, &quot;coredb&quot;)<br>            .build()<br></pre><p>Anytime you want to access your data you only need to call the function made above:</p><pre>val sneaker = snkrsDB<br>.sneakersDAO<br>.getSneakerById(5)</pre><h4>Code Generation</h4><p>Room is great because it allows you to access your DAOs which will have auto-generated implementations of all your db operations (no more cursors/content values necessary). Developers can now focus on writing beautiful features rather than binding parameters to sql statements or handwriting all the content values boilerplate that was previously required.</p><h4>Native RxJava Support (It’s Reactive!)</h4><p>Let’s go back to our query. The way we currently have it written we would only be able to access the data on a background thread (in a blocking fashion). Rarely would you want data on a background thread. Instead, it would be nice to have all database calls be observables similar to how retrofit allows observable call adapters.</p><p>The function can now be changed to return a Flowable&lt;Sneakers&gt; rather than just a Sneaker scalar value</p><pre>    @Query(&quot;Select * from Sneaker where id like :id&quot;)<br>    abstract fun getSneakerById(id: String): Flowable&lt;Sneaker&gt;</pre><p>Now the data can be accessed with all the power of RxJava</p><pre>snkrsDB<br>.sneakersDAO<br>.getSneakerById(5)<br>.toObservable<br>.observeOn(Schedulers.io())<br>.subscribeOn(AndroidSchedulers.mainThread())<br>.subscribe({updateScreen(it)},{logError{it})</pre><p>The biggest advantage of exposing queries as “endless streams” is that an update to the database will trigger a new emission to the emission above.</p><p>Anytime another insert/delete/update is run that will update the Sneaker color, my getSneakerByID backed observable will emit a new value. Additionally, schedulers can be used to allow database access on a background thread and responses to be delivered on the main thread.</p><h4>Paging Support</h4><p>So far there has been only a single record from the Sneaker table. What if instead all Sneakers need to be shown in a feed? Room allows writing a query like the one below to return all Sneakers:</p><pre>@Query(&quot;Select * from Sneaker&quot;)<br>    abstract fun getSneakerById(id: String): Flowable&lt;List&lt;Sneaker&gt;&gt;</pre><p>This doesn’t scale very well since the table could have thousands of records in it. Luckily the architecture components team integrated the <a href="https://developer.android.com/topic/libraries/architecture/paging/">Paging Library</a> with Room.</p><p>Now a pagedList can be built that will bind to a RecyclerView rather than returning all records:</p><p>First, return a DataSource.Factory:</p><pre>@Query(&quot;Select * from Sneaker&quot;)<br>    abstract fun getSneakerById(id: String): DataSource.Factory&lt;Int, Sneaker&gt;</pre><p>Then build up your PagedList, in our case we return 10 items per page:</p><pre> val config = PagedList<br>        .Config.Builder()<br>        .setPageSize(10)<br>        .setInitialLoadSizeHint(20)<br>        .build()<br> <br>val dataSourceFactory = snkrsDB<br>        .sneakerDAO()<br>        .getPage()<br>       <strong><br></strong>val pages = RxPagedListBuilder(<br>        dataSourceFactory, config)<br>        .buildObservable()</pre><h4>Testing Support(!!!!)</h4><p>One of the most exciting features of room is that it provides a straightforward API for writing connected JUnit tests. While you can’t do unit testing due to differences in sqlite on android/jvm, you can write connected junit tests that work flawlessly with Room. Here’s an example:</p><p>First you create an instance of your DB as an in memory database:</p><pre>val snkrsDB = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(), SnkrsDB::class.<em>java</em>).build()</pre><p>Next, you work with your DB same as you would in your production app</p><pre>val model=testSneaker(id=5,...)<br>snkrsDB.sneakerDAO.insert(model)<br>asssertThat(snkrsDB.sneakerDAO.getByID(5)).isEqualTo(model)</pre><p>TDD with SQL is now a reality and dramatically sped up my development time.</p><h4>Type Converters:</h4><p>Want to save a date to the db as a long? You can write a type converter for how that works:</p><pre>object MyConverters {<br>    <br>    @TypeConverter<br>    @JvmStatic<br>    fun toDate(value: Long?): Date? {<br>        return if (value == null) null else Date(value)<br>    }<br><br>    @TypeConverter<br>    @JvmStatic<br>    fun toLong(value: Date?): Long? {<br>        return value?.<em>time<br>    </em>}<br><br>}</pre><p>Similar you can have type converters for saving a list of strings to a single column:</p><pre>fun stringAdapter(): JsonAdapter&lt;List&lt;String&gt;&gt; {<br>    val listOfStringsType = Types.newParameterizedType(List::class.<em>java</em>, String::class.<em>java</em>)<br>    val adapter: JsonAdapter&lt;List&lt;String&gt;&gt; = <em>moshi</em>.adapter(listOfStringsType)<br>    return adapter<br>}<br><br>@TypeConverter<br>@JvmStatic<br>fun toJSON(list: List&lt;String&gt;?): String? = stringAdapter().toJson(list)<br><br>@TypeConverter<br>@JvmStatic<br>fun fromJson(list: String): List&lt;String&gt;? = stringAdapter().fromJson(list)</pre><p>Notice that we used Moshi to marshal/unmarshal the String</p><p>We can now add the Type Converter to our data class which will tell Room how to save/read the field from db</p><pre>@Entity<br>@TypeConverters(value = [MyConverters::class])<br>data class Sneaker(<br>        @PrimaryKey val id: String,<br>        val sizes: List&lt;String&gt;<br>)</pre><p>It’s really nice to be able to encode the type converter logic and never have to worry about it again.</p><h4>1:1 relationships with Embedded</h4><p>Let’s say our Sneaker needs has a 1:1 relationship with a barcode, we can leverage an Embedded annotation which will save the barcode to the same table as the Sneaker:</p><pre>data class Sneaker(<br>        @PrimaryKey val id: String,<br>        @Embedded val barcode:Barcode<br>)</pre><p>Anytime we insert or query a Sneaker from Room, it will properly set the Barcode field with any scalar values the Barcode contains. Think of it as flattening and unflattening the two objects. The only caveat is that Barcode &amp; Sneaker cannot have any fields that are named the same (more on that in the Bad/Ugly part)</p><h3>The Bad</h3><h4>No support for saving nested objects</h4><p>Most annoyingly, there is no support for saving nested objects. Even if we define the relations and embedded objects above, we still will need to iterate through our network models and save each object separately. Hopefully, the arch component team can improve on this process.</p><h4>Embedded fields need unique names</h4><p>Back to our embedded example above. If Barcode contains a field called name and Sneakers contains a field called name you will get an error on compile. Since Embedded fields are saved to the same table you need to have unique names for each field. One workaround is to manually rename the column in one object or another like the following:</p><pre>@ColumnInfo(name = &quot;barcodeName&quot;) val name: String?,</pre><p>Another way is:</p><pre>@Embedded(prefix = barcode)</pre><p>which will then prefix all columns pertaining to barcode to barcode<strong>*FIELDNAME*</strong></p><h4>No support for recursive relations</h4><p>Even if we go through the trouble of creating the above “view” of data, we still cannot load recursive relationships. For example, if a FeedItem contains images and barcodes, Room’s compilation will fail if each barcode contains an image as well.. This lead us to have to lazy load a Barcode’s images on our own.</p><h3>The Ugly</h3><h4>1 to many relations are pretty messy</h4><p>As mentioned at the top of the post, we are migrated from a data schema where we saved each Feed Item in a single table where all nested objects are serialized to json strings. Unless we rewrote every caller of the old sqlite db, we need to support callers that expect to get “fat models” back from room. Unfortunately this is not easy. Below are steps we needed to take to return a FeedItem which contained 1:1 &amp; 1:many relationships 4 levels deep:</p><p>Need to make views</p><ol><li>views should contain all 1:1 as embedded &amp; all 1:many as relations</li><li>all relations need to be nullable since they get selected after embeddeds</li><li>entities need to have a second constructor for nullable fields</li><li>relation fields need to be marked as @ignore and nullable</li></ol><p>Below is an example of a data schema that is similar to what we have in our app. Below are the additional objects and annotations you’d need to create if you wanted to load the following FeedItem with:</p><ul><li>1:1 relationship with a Barcode</li><li>1:many relationship with Other linked items</li><li>1:many relationship with properties</li><li>Properties that have a 1:many relationship with Images</li></ul><pre>data class FeedItemFull(<br>        @Embedded var thread: FeedItem,<br>        @Embedded var barcode: BarcodeFull?<br>) {<br>    @Relation(entity = LinkedItem::class, parentColumn = &quot;itemId&quot;, entityColumn = &quot;itemId&quot;)<br>    lateinit var relations: List&lt;LinkedItem&gt;<br>    @Relation(entity = Property::class, parentColumn = &quot;itemId&quot;, entityColumn = &quot;itemId&quot;)<br>    lateinit var properties: List&lt;PropertFull&gt;<br>}<br><br>class BarcodeFull(@Embedded<br>                  var barcode: Barcode? = null) {<br><br>    @Relation(entity = Barcode::class, parentColumn = &quot;ItemId&quot;, entityColumn = &quot;itemId&quot;)<br>    var barcode: List&lt;Barcode&gt;? = null<br>}<br><br>data class PropertyFull(@Embedded val property: Property) {<br><br>    @Relation(parentColumn = &quot;propertyId&quot;, entityColumn = &quot;propertyId&quot;)<br>    var images: List&lt;Image&gt;? = null<br>}</pre><p>We were hoping that Room would “just work” when it comes to relationships and be able to save/load nested objects without all the ceremony above. Hopefully someday we can get to a better place with this. We considered writing an annotation processor to save FeedItems which would, in turn, save all nested complex objects to their own table.</p><h4>Conclusion</h4><p>After using Room for the first time we can definitely say that the good far outweigh the bad. The biggest pain point is how relationships are handled, otherwise doing sql on android is dramatically easier than its ever been. We will continue to migrate more and more of our data layer to Room and hope that the always amazing Architecture Component team will continue to make improvements</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8cc759b312cb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/s23nyc-tech/room-with-a-view-8cc759b312cb">Room with a View</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>
        <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----f165243cf853---4</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>
        <item>
            <title><![CDATA[Data Model as your Table Stakes]]></title>
            <link>https://medium.com/s23nyc-tech/data-model-as-your-table-stakes-6937c95e7039?source=rss----f165243cf853---4</link>
            <guid isPermaLink="false">https://medium.com/p/6937c95e7039</guid>
            <category><![CDATA[data]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[immutable]]></category>
            <category><![CDATA[okhttp]]></category>
            <dc:creator><![CDATA[Brian Plummer]]></dc:creator>
            <pubDate>Fri, 06 Jul 2018 14:21:45 GMT</pubDate>
            <atom:updated>2018-07-06T14:35:00.556Z</atom:updated>
            <content:encoded><![CDATA[<p>We developers are often in a rush to create our network/api models which results in many on-device crashes and time wasted recompiling our apps. I’ve found that building and testing your model objects locally before deployment saves you time and effort. When I connect to a REST endpoint this is how I do it.</p><p>We’ll be using Kotlin data classes with <a href="https://github.com/square/moshi">Moshi</a> to create value objects that are tested, have generated type adapters, and correctly handle api responses from a particular endpoint. We’ll also be using <a href="https://github.com/square/okio">Okio</a> to load our data from a test.</p><h3>Collecting sample json</h3><p>Let’s start with a <a href="http://data.consumerfinance.gov/api/views.json">consumer complaint database</a> for our example, feel free to use your own. First step is to fire up a browser to inspect the initial json from the link above. It is a good idea to format our sample json by pasting it into your favorite json editor/formatter. Additionally you will want to copy the formatted json into your test resources folder and save it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/550/1*di3bg23nJ_vJ_c38ntk5_w.png" /></figure><p>I’ve found doing this over the years to be invaluable. The formatted json acts a reference for yourself and future developers. We can test against it and not rely on any external systems. If the backend goes down, our tests will still pass. If anything does happen to change on the server we have a great reference in our tests to help us understand those changes.</p><h3>Modeling your data</h3><p>Let’s get started on the data model! We’re going to be using <a href="https://github.com/square/moshi/tree/master/kotlin/codegen">Moshi’s Kotlin code gen package</a> to create a model from Kotlin data classes. Why Moshi? It provides type adapters for us as you would typically have to use a library with gson like <a href="http://immutables.github.io/">Immutables.org</a> or <a href="https://github.com/google/auto/blob/master/value/userguide/index.md">Auto Value</a>. Also, Moshi respect’s the nullability rules of Kotlin and will disallow a null value in a non null field.</p><p>Kotlin data classes are also immutable. As mentioned, <a href="https://speakerdeck.com/brianplummer/immutability-with-immutables">this makes our lives easier in a lot of ways.</a> By not having to worry about data mutations we reduce complexity around working with data, particularly in a multi threaded environment like Android. If we did want to have the equivalence of a mutation, we can simply use the builtin copy method that we get for free from using Kotlin data classes.</p><p>We need to add in our dependencies like:</p><pre>implementation &quot;<strong>com.squareup.moshi:moshi:$moshi_version&quot;<br></strong>kapt(&quot;<strong>com.squareup.moshi:moshi-kotlin-codegen:$moshi_version&quot;</strong>)</pre><p>Looking at the json data that we got from the url above, we can see that it is made up of an array of complaints with usual scalar values like id, description, createdAt and some json objects like owner and metadata.</p><pre>@JsonClass(generateAdapter = <strong>true</strong>)<br><strong>data class </strong>Complaint (<br>        <strong>val id</strong>: String,<br>        <strong>val description</strong>: String,<br>        <strong>val createdAt</strong>: Long,<br>        <strong>val owner</strong>: Owner<br>)</pre><pre>@JsonClass(generateAdapter = <strong>true</strong>)<br><strong>data class </strong>Owner (<br>        <strong>val id</strong>: String,<br>        <strong>val displayName</strong>: String,<br>        <strong>val flags</strong>: List&lt;String&gt;<br>)</pre><p>Notice the @JsonClass(generateAdapter = true). That’s how the magic happens — Moshi picks up this annotation and generates all of your type adapters for you. <a href="https://medium.com/@sweers">Zac Sweer</a>s <a href="https://medium.com/@sweers/exploring-moshis-kotlin-code-gen-dec09d72de5e">wrote an excellent article</a> that goes into the code generation internals and advanced usage. I encourage you to check it out.</p><h3>Loading json for a unit test</h3><p>Next, let’s add in <a href="https://github.com/square/okio">Okio</a> as a dependency as we’ll need it to load the sample json file from earlier. Why are we doing this again? We want to load the json response to test against and we don’t want to have to hit the server each time.</p><pre>implementation &quot;<strong>com.squareup.okio:okio:$okio_verison&quot;</strong></pre><p>I’ve had a hard time consistently accessing the file system during tests and have found that the best way is to do something like this:</p><pre><strong>private fun </strong>inputStream(path: String): InputStream {<br>    <strong>return </strong>ComplaintsTest::<strong>class</strong>.<em>java</em>!!.getResourceAsStream(path)<br>}</pre><pre>@Test<br><strong>fun </strong>loadData () {<br>    <strong>val </strong>myStream = inputStream(<strong>&quot;/gov_complaints.json&quot;</strong>)<br>    Assert.assertNotNull(myStream)<br>}</pre><p>You can see in the above example that we are a getting a <a href="https://kotlinlang.org/docs/reference/reflection.html#class-references">runtime reference</a> to the Kotlin test class and using that load the data file as a stream: ComplaintsTest::class.java!!.getResourceAsStream(path)</p><p>Now that we can access our data on disk, let’s use Moshi to get it into the form we want. But first, how do we handle that stream? We can see Moshi’s fromJson() method which takes either a JsonReader, BufferedSoruce, or a String. Okio makes transforming a stream a simple one liner like this:</p><pre>Okio.buffer(Okio.source(inputStream(<strong>&quot;/gov_complaints.json&quot;</strong>)))</pre><p>That’s it! we’re good to go! You now have a buffered source that Moshi can read from. If you’ve never had to deal with a raw list in Moshi, you do it with a ParameterizedType like this:</p><pre>val listType = Types.newParameterizedType(List::<strong>class</strong>.<em>java</em>, Complaint::<strong>class</strong>.<em>java</em>)</pre><p>And create the Moshi adapter like so:</p><pre>moshi.adapter&lt;List&lt;Complaint&gt;&gt;(listType)</pre><p>Putting this altogether we get the following:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3e0c9aefe7264c10fce25a7da720fef0/href">https://medium.com/media/3e0c9aefe7264c10fce25a7da720fef0/href</a></iframe><h3>Validating your data model</h3><p>Running our test fails immediately with the following error:</p><pre>JsonDataException: Required property ‘flags’ missing at $[0].owner</pre><p>The flags value in one of our json samples is either missing or null which breaks Kotlin’s non nullable type that we declared in our data model. We should probably make this optional. We’ll go ahead and add a “?” on the flags value. We finally end up with:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f8bf17112490fff59570488abb4d0545/href">https://medium.com/media/f8bf17112490fff59570488abb4d0545/href</a></iframe><p>Running our test over and over again will allow us to validate and fix any missing types. While sometimes you have a json schema, in our case this is a great way to prevent crashes at runtime. When we connect to the endpoint or pass that work to another developer, a good bit of the leg work will already be done and time spent on figuring this out will be kept to a minimum. Any issues or inconsistent data can be caught early.</p><h3>Summary</h3><p>Regardless of how you are getting the data or what libraries you are using, this pattern of steps will be applicable to you. Over many years of working on a variety of applications large and small, I have found that this has saved me time and effort again and again. The next time you have to model some data, follow these steps. You’ll impress your coworkers and bring some sanity back.</p><p>To summarize:</p><ul><li>Collect json samples, format them, add them to test resources folder and check them into source control. You now have human-readable datasets to test against and refer to as needed.</li><li>Design the data model from a unit test, resulting in less turnaround time and reducing overall time spent on this task.</li><li>Load the datasets via tests as this will allow you to validate any mapping logic you require for your data model.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_NN3Yc31XsV-N2ijnS6VLA.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6937c95e7039" width="1" height="1" alt=""><hr><p><a href="https://medium.com/s23nyc-tech/data-model-as-your-table-stakes-6937c95e7039">Data Model as your Table Stakes</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>
        <item>
            <title><![CDATA[ARKit Planes, 3D Text and Hit Detection]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/s23nyc-tech/arkit-planes-3d-text-and-hit-detection-1e10335493d?source=rss----f165243cf853---4"><img src="https://cdn-images-1.medium.com/max/600/0*OsPX2pZg8ZBFMDa3." width="600"></a></p><p class="medium-feed-snippet">In Part 2 of the series, we built up on our AR scene by providing some interactivity, and getting more familiar with SceneKit. We also&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/s23nyc-tech/arkit-planes-3d-text-and-hit-detection-1e10335493d?source=rss----f165243cf853---4">Continue reading on S23NYC: Engineering »</a></p></div>]]></description>
            <link>https://medium.com/s23nyc-tech/arkit-planes-3d-text-and-hit-detection-1e10335493d?source=rss----f165243cf853---4</link>
            <guid isPermaLink="false">https://medium.com/p/1e10335493d</guid>
            <category><![CDATA[scenekit]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[augmented-reality]]></category>
            <category><![CDATA[ar]]></category>
            <category><![CDATA[arkit]]></category>
            <dc:creator><![CDATA[Dan Wyszynski]]></dc:creator>
            <pubDate>Fri, 22 Jun 2018 18:21:37 GMT</pubDate>
            <atom:updated>2018-10-23T18:04:41.435Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Store+Room — A Reactive Repository]]></title>
            <link>https://medium.com/s23nyc-tech/storeroom-a8d6391bccb7?source=rss----f165243cf853---4</link>
            <guid isPermaLink="false">https://medium.com/p/a8d6391bccb7</guid>
            <category><![CDATA[room]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[repository-pattern]]></category>
            <dc:creator><![CDATA[Mike Nakhimovich]]></dc:creator>
            <pubDate>Wed, 30 May 2018 16:59:38 GMT</pubDate>
            <atom:updated>2018-05-30T18:29:17.072Z</atom:updated>
            <content:encoded><![CDATA[<h3>Store: a brief history</h3><p>One of the first libraries I open sourced was <a href="https://github.com/NYTimes/Store">Store</a> which was an implementation of the Repository pattern wrapped in RxJava. Store abstracted away the common pattern of<a href="https://github.com/NYTimes/Store/blob/feature/rx2/Images/store-5.jpg"> <em>load from memory, if not present load from disk parse the data, if not present load from network</em></a>. This was all done with minimal setup by a user:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b8002bffa3ca31ee068f4084ba096133/href">https://medium.com/media/b8002bffa3ca31ee068f4084ba096133/href</a></iframe><p>Initially Store was something I developed based on my use cases. While everything was interface based, I shipped a few implementations of parsers for <a href="https://github.com/NYTimes/Store/tree/feature/rx2/middleware">Gson</a>, <a href="https://github.com/NYTimes/Store/tree/feature/rx2/middleware-moshi">Moshi</a>, <a href="https://github.com/NYTimes/Store/tree/feature/rx2/middleware-jackson">Jackson</a>) and a <a href="https://github.com/NYTimes/Store/blob/feature/rx2/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersister.java">FilePersister</a> which eased in streaming data from a network to disk.</p><p>Fast forward 18 months and Store is up to 3000+ stars &amp; 37 contributors mostly from folks I’ve never met (Thank you all!). In that time the community has helped implement:</p><ul><li><a href="https://github.com/NYTimes/Store/blob/feature/rx2/store/src/main/java/com/nytimes/android/external/store3/base/impl/MemoryPolicy.java">Memory</a> &amp; <a href="https://github.com/NYTimes/Store/blob/feature/rx2/store/src/main/java/com/nytimes/android/external/store3/base/impl/StalePolicy.java">disk caching</a> policies</li><li><a href="https://github.com/NYTimes/Store/blob/feature/rx2/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiTransformerFactory.java">Wrappers for fetchers that return full objects rather than streams</a></li><li><a href="https://github.com/NYTimes/Store/blob/ed378585c20b8f1c53986d6a2c9875a6fafb58b6/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.java#L34">Result abstractions to determine which source data is coming from</a></li><li><a href="https://github.com/NYTimes/Store/blob/feature/rx2/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.java#L42">New API for refreshing get calls on clearing data</a></li><li><a href="https://github.com/NYTimes/Store/blob/feature/rx2/store-kotlin/README.md">A fluent Kotlin DSL</a></li><li><a href="https://github.com/NYTimes/Store/wiki">Documentation including translations to Russian &amp; Spanish</a></li><li><a href="https://github.com/NYTimes/Store/pull/155">An update to RxJava2</a></li><li><a href="https://github.com/NYTimes/Store/pulls?q=is%3Apr+is%3Aclosed">Many other PRs full of performance improvements and bug fixes</a>.</li></ul><p>Store as it stands today is a full fledged data solution that both helps prevent extraneous data calls to your network and helps write apps that exhibit proper separation of concerns. Since moving on from the New York Times, I’ve had a chance to rethink how Store can better suit the needs of users in the modern Android ecosystem. I’m not just the primary maintainer of Store, I also use it in every project I have built since.</p><h3>Time to get a Room</h3><p>One of the most <a href="https://github.com/NYTimes/Store/issues/215">popular open source requests Store had</a> was integration with <a href="https://developer.android.com/topic/libraries/architecture/room">Room</a>. I was hesitant to integrate Room because I had never used it before and I wasn’t really sure how the integration would work. Now that I’m working at Nike’s s23NYC studio, we explored what data solution best fits into our re-architecting of the SNKRS Android app that we maintain. After exploring various solutions we decided that Room was by far the most robust and easy to use persistence solution. While Room was great at working with a database, it was still missing key features like request de-duping, memory caching, stale/refresh policies and all the other great features mentioned above. We still needed a repository abstraction that would wrap Room to implement these features. So, my team set out to fuse all the great features of Room with all the things we loved from Store.</p><p>Since most of Store was based on interfaces, we were able to integrate Room into Store by building a RoomPersister and StoreRoom, which is a specialized type of store. The StoreRoom includes the familiar features from Store while taking advantage of the reactive nature of Room &amp; Flowables.</p><h3>Room: Remind me what that is?</h3><p>Room is a persistence library that is part of Google’s Architecture Components. Room is an ORM and does a fantastic job of giving you all the power of working with a database without any of the messiness of cursors, database connectors or having to map data yourself. Room is built on top of SQLite and takes care of a lot of the boilerplate associated with saving to or reading from a database.</p><p>Additionally, Room’s reactive API through its first class RxJava support transforms simple SQL queries into auto updating Flowables. Room queries exposed as RxJava Flowables is what I’ve always imagined as the perfect persistence layer to Store. It helped me get rid of messy code like the old stream &amp; getRefreshing functions we previously used to fake endless streams. Using a Room backed persister allows Store subscriptions to update anytime the underlying data changes.</p><h3>Store+Room a match made in Android heaven</h3><p>Using the new StoreRoom is every bit as easy as using a regular Store.</p><p>First, create a Room DAO as you always do with Room. Within your DAO, expose an Insert &amp; a Query function. The types do not need to be the same.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6e6de1c3fa8fea65130c3ab6da474d98/href">https://medium.com/media/6e6de1c3fa8fea65130c3ab6da474d98/href</a></iframe><p>Next, implement Store’s Fetcher to tell StoreRoom how to get new network data. Below is an example that would use an API (Retrofit is recommended) to return a Single&lt;User&gt; based on a passed in user id.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8e95e7b8fa2ebfc918e131b9675a441c/href">https://medium.com/media/8e95e7b8fa2ebfc918e131b9675a441c/href</a></iframe><p>Since both Retrofit &amp; Room work with objects, there’s no reason to implement a parser anymore. We will let Retrofit take care of the parsing when a new network request comes in.</p><p>Next, we need to make an implementation of our RoomPersister. Notice how the read function returns a List&lt;User&gt; while the write function is only writing a single user each time.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/967502621fc90ffdbffd01adc2406130/href">https://medium.com/media/967502621fc90ffdbffd01adc2406130/href</a></iframe><p>You can of course change what the read and write functions return. As another example, you might want to write a collection or return a single item — the choice is up to you.</p><p>Now that we have our Fetcher &amp; Persister, we can create an implementation of a StoreRoom:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/26f3b5feeadd404430445fe72bbe7cbe/href">https://medium.com/media/26f3b5feeadd404430445fe72bbe7cbe/href</a></iframe><p>Voila! All the functionality we always had with Stores but rather than being backed by a file persister we are now backed by a Room DAO.</p><p>Want to get data but don’t care about the source? Call store.get(). Need to bust through your cache and call network only? Call store.fresh()</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0502796e7e698bb8dbb7f37eda175429/href">https://medium.com/media/0502796e7e698bb8dbb7f37eda175429/href</a></iframe><p>So far this feels just like a normal Store. What do we gain by using Room besides ability to query &amp; have different read/write types? Reactive reads, that’s what!</p><p>Since Room can expose data as Flowables, StoreRoom will expose endless reactive streams from your get and fetch calls. For example, if you call store.get() and subscribe to the results, anytime another call is made to your stores’ get or fetch method, your subscriber receive an updated value.</p><p>With StoreRoom we not only have disk and memory caching but we now have endless observables that will auto update subscribers if the underlying data we are querying for changes. Particularly in the above example every new user that gets fetched from the network and saved to the DB will cause an emission of all the users to any current subscribers.</p><p>Store and Room are a match made in heaven, Room is inherently reactive as the use case above demonstrates while Store gives you memory that sits on top of those DB requests and missing features like request throttling when you request the same data in parallel from various parts of your app (Think of reading the same config value N times on app start without having to hit your network or db more than once). As always with Store, new subscribers to Store.get() don’t need to go all the way to your DB if your memory cache has not expired. This leads to my ideal data workflow of fast reads from memory and reactive reads from disk all backed by a network source.</p><h3>What’s next</h3><p>I hope you enjoy using our new StoreRoom and RoomPersisterthat our s23NYC team is contributing back to Store. Next, we hope to work towards an annotation processor that would auto generate the above Store code from a Room DAO. As always community help is appreciated!</p><p>Resources:</p><p><a href="https://github.com/NYTimes/Store">NYTimes/Store</a></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FTvsOsgd0--c%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DTvsOsgd0--c&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FTvsOsgd0--c%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/27beb34e1b6bc5d934414d3f1905e28c/href">https://medium.com/media/27beb34e1b6bc5d934414d3f1905e28c/href</a></iframe><ul><li><a href="https://medium.com/androiddevelopers/7-steps-to-room-27a5fe5f99b2">7 Steps To Room</a></li><li><a href="https://medium.com/google-developers/room-rxjava-acb0cd4f3757">Room 🔗 RxJava</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a8d6391bccb7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/s23nyc-tech/storeroom-a8d6391bccb7">Store+Room — A Reactive Repository</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>
        <item>
            <title><![CDATA[ARKit, SceneKit, and How to Control the World]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/s23nyc-tech/arkit-scenekit-and-how-to-control-the-world-dc7377eb1dc6?source=rss----f165243cf853---4"><img src="https://cdn-images-1.medium.com/max/600/0*QKXe2oYQNOQ-klDo." width="600"></a></p><p class="medium-feed-snippet">In Part 1 of this series, we went through a workflow where we processed a 3D model, created an AR project in Xcode, started an AR session&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/s23nyc-tech/arkit-scenekit-and-how-to-control-the-world-dc7377eb1dc6?source=rss----f165243cf853---4">Continue reading on S23NYC: Engineering »</a></p></div>]]></description>
            <link>https://medium.com/s23nyc-tech/arkit-scenekit-and-how-to-control-the-world-dc7377eb1dc6?source=rss----f165243cf853---4</link>
            <guid isPermaLink="false">https://medium.com/p/dc7377eb1dc6</guid>
            <category><![CDATA[arkit]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ar]]></category>
            <category><![CDATA[augmented-reality]]></category>
            <category><![CDATA[scenekit]]></category>
            <dc:creator><![CDATA[Dan Wyszynski]]></dc:creator>
            <pubDate>Mon, 16 Apr 2018 14:04:21 GMT</pubDate>
            <atom:updated>2018-04-16T14:04:21.218Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Prototyping Animations in Swift]]></title>
            <link>https://medium.com/s23nyc-tech/prototyping-animations-in-swift-97a9cfb1f41b?source=rss----f165243cf853---4</link>
            <guid isPermaLink="false">https://medium.com/p/97a9cfb1f41b</guid>
            <category><![CDATA[caanimation]]></category>
            <category><![CDATA[animation]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[prototyping]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Jason Wilkin]]></dc:creator>
            <pubDate>Tue, 10 Apr 2018 14:23:46 GMT</pubDate>
            <atom:updated>2018-04-10T14:23:46.005Z</atom:updated>
            <content:encoded><![CDATA[<p>One of my favorite things about building mobile applications is bringing a designer’s creation to life. Being able to harness the power of the iPhone and create experiences that delight users is one of the reasons I wanted to become an iOS developer. So when the design team at s23NYC came to me with the animation prototype for SNKRS Pass I was excited but also very scared:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*3HOg8B2Wgg8aYx-jt3VJkw.gif" /></figure><p>Where to begin? It can be a daunting question when looking at a complex animation mock-up. In this blog post we will break down an animation into pieces and prototype iteratively to build a reusable, animated wave view.</p><h3>Prototyping in a Playground</h3><p>Before we dive in, it will be useful to setup an environment where we can rapidly prototype our animation without having to continuously build and run with every small change we make. Luckily, Apple gave us Swift Playgrounds, which is the perfect place to sketch out front-end code quickly without having to use a full application container.</p><p>Let’s create a new Playground in Xcode by selecting File &gt; New &gt; Playground… from the menu bar. We can choose the <strong>Single View</strong> playground template to get the Playground live view code written for us in a nice template. We’ll make sure to select the Assistant Editor so that we can see live updates as we code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-zmk2zyLGlQQUJPBO3JCbg.png" /></figure><h3>Animating Waves</h3><p>This animation we’re building is one of the final parts of the SNKRS Pass experience, which is a new way to reserve access to the newest and hottest Nike shoes at retail stores. When a user goes to pick up their shoes, we want to give them a digital pass that feels like a golden ticket. The background animation is meant to mimic a holographic sticker of authenticity. When the user tilts the device, the animation reacts, and moves around as if a light is reflecting off of it.</p><p>Let’s start very simply by creating some concentric circles:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e92c190619d6ae1cd00d4449c6ad7037/href">https://medium.com/media/e92c190619d6ae1cd00d4449c6ad7037/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*HyJxGRHIus_TkVvL2uW5MA.png" /></figure><p>Easy enough. Now how to animate these outwards? We’ll make use of CAAnimation and Timer to continuously add and animate these CAShapes. There are two parts to this animation: scaling the shape’s path and increasing the shape’s bounds. It’s important to animate the bounds in tandem with a scale transform on the shape so that the circle moves to fill the screen. If we don’t animate the bounds, the circles would expand while keeping their initial origin at the center of the view (expanding down to the lower right corner). So let’s add both of these animations to an animation group to perform them at the same time. It’s important to remember that CAShape and CAAnimation require converting UIKit values to their CGPath and CGColor counter parts. Otherwise, the animation will just fail silently! We’ll also make use of the CAAnimation delegate method animationDidStop to remove the shape layer from the view once its animation completes.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1bab1567da647257896818b9475e959a/href">https://medium.com/media/1bab1567da647257896818b9475e959a/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/0*S_Gt_5S6BNy3fdIE." /></figure><p>Trippy! Next, we will swap out the circles for our custom path. In order to generate a custom path, we can use <a href="https://www.paintcodeapp.com/">PaintCode</a> to help generate code. In this blog post, we’ll be using a star shape for our wave path:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/280fb21d7bacc6cce593b3348c636798/href">https://medium.com/media/280fb21d7bacc6cce593b3348c636798/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*plbhAHOZPINSokz4MARg8w.png" /><figcaption>(Not to scale)</figcaption></figure><p>The tricky part with using a custom path is that we now need to scale this path instead of generating a final circle path from the bounds of the AnimatedWaveView. Since we want this view to be reusable, we need to calculate how much to scale the shape’s path and bounds based on the final destination rect. We can create a CGAffineTransform based on the ratio of the path’s final bounds to its initial bounds. We also multiply this ratio by the <em>scaleFactor</em> of 2.25 so that the path expands larger than the view before completing. We need to do this so that the shape completely fills the corners of our view, rather than just disappearing once it reaches the view’s size. Let’s build the initial and final paths during initialization and update the final path if our view’s frame changes:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/87875522e4674fa8847c33004492a93d/href">https://medium.com/media/87875522e4674fa8847c33004492a93d/href</a></iframe><p>After updating our animation group to use our new <em>finalPath</em> property and using the <em>initialPath</em> inside <em>buildWave()</em>, we’ll have an updated path animation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*W7v-DukdmbIgkVf3htpTMQ.gif" /></figure><p>The final piece to making sure we can reuse this wave animation in different sizes is to refactor the Timer approach. Rather than continuously creating new waves, we can create all of the waves at once and stagger the start time on CAAnimation. This is done by setting the <em>timeOffset</em> on the CAAnimation group. By giving each animation group a slightly different <em>timeOffset</em>, we run all animations in parallel from different starting points. We will calculate the offset by dividing the total duration of the animation by the number of waves on screen:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7c3306500bf68b506c6567b75f8af5aa/href">https://medium.com/media/7c3306500bf68b506c6567b75f8af5aa/href</a></iframe><p>We’ll pass the <em>duration</em> and <em>timeOffset</em> down to the <em>animateWave()</em> method. Let’s add in a fade-in animation as part of the group to make things a bit smoother:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f6b989d8d0651dddcc8282c080bc0dca/href">https://medium.com/media/f6b989d8d0651dddcc8282c080bc0dca/href</a></iframe><p>Now we can draw each waves and add animations all at once when calling <em>makeWaves().</em> Let’s take a look at our hard work:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*NKetxE_MBLhJrpiOgj4rHg.gif" /></figure><p>Woohoo! We now have a reusable animated wave view!</p><h3>Adding a Gradient</h3><p>The next step is to improve our wave animation by adding a gradient. We’d also like to animate the gradient in relation to device motion, so we will create a gradient layer and keep a reference to it. I explored putting semi-transparent wave layers on top of the gradient but the best solution was to add the wave layers to a parent layer and set it as the mask of the gradient layer. With this approach, the parent layer draws the gradient itself, which looks much more effective:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*BF921yl9t9RX0agWCnNWrA.gif" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/95dcbb71056b1260217bf34eb806a606/href">https://medium.com/media/95dcbb71056b1260217bf34eb806a606/href</a></iframe><h3>Motion Tracking</h3><p>The next step is animating the gradient to move in correlation with device motion tracking. We want to create a holographic effect that mimics light reflecting against the surface of the view as you tilt it in your hands. To achieve this, we will add a gradient which rotates around the center of the view. We will use CoreMotion and the CMMotionManager to track accelerometer updates and use this data for our interactive animation. NSHipster has a <a href="http://nshipster.com/cmdevicemotion/">fantastic write-up on CMDeviceMotion</a> if you’re looking for a deeper dive into what CoreMotion has to offer. For our AnimatedWaveView, we will only need the CMDeviceMotion <em>gravity</em> property (CMAcceleration) which will give us the acceleration velocity of the device. We will only need to track the X and Y axis as the user tilts the device horizontally and vertically:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*f_KMSGaHGcgAV6_W_OP6NQ.png" /><figcaption><a href="https://developer.apple.com/documentation/coremotion/getting_raw_accelerometer_events#2904020">https://developer.apple.com/documentation/coremotion/getting_raw_accelerometer_events#2904020</a></figcaption></figure><p>So X and Y will be from -1 to +1 with (0,0) being the origin (device resting flat on a table face up). Now how do we want to use this data?</p><p>At first, I tried using CAGradientLayer and thought rotating the gradient would create this shimmering effect. We could update its <em>startPoint</em> and <em>endPoint</em> based on the CMDeviceMotion <em>gravity</em>. CAGradientLayer is a linear gradient, so revolving <em>startPoint</em> and <em>endPoint</em> around the center will effectively rotate the gradient. Let’s convert the X and Y values from <em>gravity</em> to the degree value we would use to rotate the gradient:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4e1b174e5103da3daa7253017abebac0/href">https://medium.com/media/4e1b174e5103da3daa7253017abebac0/href</a></iframe><p>Note: We can’t simulate motion tracking in the simulator or in a Playground so we’ll need to switch to working in a Xcode project with a real device.</p><p>After some initial testing with design, we felt the need to add a booster to the X value returned from <em>gravity</em> so that the gradient would rotate at a faster rate. So we multiply the <em>gravity.x </em>before converting to radians.</p><p>To perform the rotation of the gradient, we’ll need to convert the angle at which the device is rotated to the start and end points of the rotation arc: <em>startPoint</em> and <em>endPoint</em> for the gradient. There is a really smart StackOverflow answer we can use to achieve this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c66b71ec43db2cdc50d4e61972996326/href">https://medium.com/media/c66b71ec43db2cdc50d4e61972996326/href</a></iframe><p>Busting out some trigonometry! Now we’ve converted degrees to our new <em>startPoint</em> and <em>endPoint</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/240/1*l_V_LhGj645oaObSZUhfTw.gif" /></figure><p>This is ok…but can we do better? Most definitely! Let’s take this to the next level…</p><p>CAGradientLayer doesn’t support radial gradients…but that doesn’t mean it can’t be done! We can use CGGradient to create our own CALayer class, RadialGradientLayer. The tricky part here is making sure to cast an array of CGColors to a CFArray during the initialization of the CGGradient. It took a bit of trial and error to figure out exactly what kind of array needed to be casted to a CFArray and that locations could simply be an array of CGFloats to satisfy the UnsafePointer&lt;CGFloat&gt;? Type.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/68c6add869825b61a53531c6ba23da35/href">https://medium.com/media/68c6add869825b61a53531c6ba23da35/href</a></iframe><p>At last we have all the pieces in place! Now we can swap out the CAGradientLayer for our shiny new RadialGradientLayer and calculate a mapping of device gravity X and Y to a coordinate position for the gradient. We’ll convert the gravity values to a float percentage between 0.0 and 1.0 to calculate how to move the gradient.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5d765e0ea6292cf6969390ce2ad9e50c/href">https://medium.com/media/5d765e0ea6292cf6969390ce2ad9e50c/href</a></iframe><p>Now let’s circle back to the <em>makeWaves</em> and <em>addGradientLayer</em> method and make sure everything is connected together:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/77e71e4a9954e6f7267d5e2d16a524bf/href">https://medium.com/media/77e71e4a9954e6f7267d5e2d16a524bf/href</a></iframe><p>Drumroll please…</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fplayer.vimeo.com%2Fvideo%2F263896775%3Fapp_id%3D122963&amp;dntp=1&amp;url=https%3A%2F%2Fvimeo.com%2F263896775&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=vimeo" width="1280" height="720" frameborder="0" scrolling="no"><a href="https://medium.com/media/70e23c10b90bf087ed0d827761650054/href">https://medium.com/media/70e23c10b90bf087ed0d827761650054/href</a></iframe><p>Now that’s pretty slick!</p><p>Attached is the the final example project with all of the code in its final state. I encourage you to try running this on a device and play around with it!</p><p><a href="https://github.com/j-wilkin/AnimatedWaveView">j-wilkin/AnimatedWaveView</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=97a9cfb1f41b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/s23nyc-tech/prototyping-animations-in-swift-97a9cfb1f41b">Prototyping Animations in Swift</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>