<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Adam Hurwitz on Medium]]></title>
        <description><![CDATA[Stories by Adam Hurwitz on Medium]]></description>
        <link>https://medium.com/@adamhurwitz?source=rss-6049ba9d2bbd------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*0yEmon3hJKcxVIXjSJeR3Q.jpeg</url>
            <title>Stories by Adam Hurwitz on Medium</title>
            <link>https://medium.com/@adamhurwitz?source=rss-6049ba9d2bbd------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 12 Nov 2026 08:26:25 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@adamhurwitz/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Medellín — Work remotely]]></title>
            <link>https://adamhurwitz.medium.com/medell%C3%ADn-work-remotely-f5a2201bfa44?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/f5a2201bfa44</guid>
            <category><![CDATA[life-hacking]]></category>
            <category><![CDATA[medellin]]></category>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[nomad]]></category>
            <category><![CDATA[latin-america]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Mon, 13 Nov 2023 05:06:33 GMT</pubDate>
            <atom:updated>2023-11-13T05:26:06.379Z</atom:updated>
            <content:encoded><![CDATA[<h3>Medellín — Work remotely</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JQTna4lQv-Ucx0Mp.png" /><figcaption>Photo credit: Me, <a href="https://wework.com.co/ubicaciones/carrera-42-5-sur-145">WeWork El Poblado</a></figcaption></figure><p><em>Note: This was originally published on </em><a href="https://hackernoon.com/my-experience-working-remotely-from-medellin"><em>HackerNoon</em></a><em> and written in 2019.</em></p><p>Working in front of a screen doesn’t change based on location. Medellín is a great place because of the opportunities to try food, the outdoors, work from amazing cafes, and the opportunity to meet diverse people.</p><p>I traveled to Medellín, Colombia, both to explore for a week on vacation and then to immerse myself in daily life while working remotely as the creator of <a href="https://github.com/adam-hurwitz/Coinverse#coinverse-open-app">Coinverse</a>, the first cryptocurrency news audiocast app.</p><p>The high standard of living is great for productivity.</p><ul><li><strong>Commuting</strong>: Convenient and safe, the metro has been running since 1995.</li><li><strong>Places to work</strong>: Choose a new cafe every day, and you’ll still not see all of them. There are plenty of coworking options too.</li><li><strong>Food</strong>: Eat locally and affordably, or choose international options.</li></ul><h3>Coffee culture</h3><p>Unlike the states, adding cream to coffee is doing your taste buds a massive disservice. The coffee is flavorful, ranging in levels of sweet, fruity, bitter, chocolate, vanilla, and creamy flavors. Espresso is the default. Learn to ask for “cafe filtrado” if seeking drip coffee.</p><p>Important factors for cafes are the coffee and atmosphere for working and hanging out. I was concerned with the availability of power outlets and the internet and discovered this was not an issue. The most expensive coffee was $2, and many places have a full food menu.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*J4sgxjEUc6QMINwi" /><figcaption>Photo credit: Me, Pardo Café</figcaption></figure><p>These are my three favorites in El Poblado, where I spent most of my time.</p><ul><li><a href="https://goo.gl/maps/jzngDRXb9dgGnMc7A">Hija Mía</a>: It is small, friendly, and comfortable. You have to try the hardy version of avocado toast. This cafe has many travelers.</li><li><a href="https://goo.gl/maps/9MCMsnPjRsPpAqkJ7">Urbania Café</a>: Each cup is served with a piece of smooth dark chocolate. They offer a full-day coffee experience on home brewing and experimenting with flavors.</li><li>Pergamino (<a href="https://goo.gl/maps/yoqXfibavTPGj3Wj6">Carrera 37</a>, <a href="https://goo.gl/maps/9LQkRN1BeFnkus9S6">Calle 10b</a>): Although it’s a chain, the coffee is great. I prefer the newer Calle 10b location as it has an open second floor which is quiet and great for focusing. They offer great gift wrapping for beans bought as gifts, too.</li></ul><p>I spent time at other great cafes in El Poblado, too.</p><ul><li><a href="https://goo.gl/maps/oYsJ1rzZzvf4uER17">Pardo</a>: This doubles as a clothing store and restaurant with big healthy salads for lunch. The iced coffee was my favorite.</li><li><a href="https://goo.gl/maps/k8hzhT6j1u21gCJW9">Cafe Velvet</a>: High-end and comfortable.</li><li><a href="https://goo.gl/maps/asQK3ECwzu6DD3FT6">Starbucks Coffee Milla de Oro</a>: This location is open the latest, 10 PM most nights, and 11 PM on Friday and Saturday.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*v79PR-XPnSPdwA7y" /><figcaption>Photo credit: Me, Cafe Velvet</figcaption></figure><p>In Laureles, I enjoyed <a href="https://goo.gl/maps/6aNarjhokJM25J6X7">Café Zeppelin</a>, with a rock and roll vibe and a great spot for meeting up, and <a href="https://goo.gl/maps/mXe9QZjDMJ63unPT7">Rituales Compañia de Café</a>, a small and trendy spot.</p><p>These come highly recommended from <a href="https://twitter.com/anthonycastrio">Anthony Castrio</a> and <a href="https://jeffanders.co/">Jeff Ander’s</a> <a href="https://docs.google.com/spreadsheets/d/1fjU6sutEcdVvbQzZbNNk4h5xQXZ4R0GqG0dV_QOmGhI/edit?rm=minimal">Cafe Grind Rankings</a>, which organizes the best cafes based on factors such as atmosphere, food, wifi, etc.</p><h4>Laureles</h4><ul><li><a href="https://goo.gl/maps/3A5kXmE6X3fAsAhD9">Café Cliché</a></li><li><a href="https://goo.gl/maps/MvDnoRJDS1BVVaTy6">Quiero Café</a></li><li><a href="https://goo.gl/maps/SnbNWosbQu7FCpZM8">Naturalia Café</a></li><li><a href="https://goo.gl/maps/Ppo6NXni4wfVqWSQ9">Café Revolución</a></li></ul><h4>Envigado</h4><ul><li><a href="https://goo.gl/maps/evozyPx7oL6APzYR6">Vizzto</a></li><li><a href="https://goo.gl/maps/WpjbmY4HjTLJjWTN7">Café Apasionado</a></li></ul><p><em>See </em><a href="https://www.google.com/maps/@6.224853,-75.5970069,13z/data=!4m3!11m2!2s1tvh5zn54hj1womBgwsDlT9KKbrU!3e3"><em>cafes map</em></a></p><h3>Coworking</h3><p>A coworking space is great for increasing productivity. Spaces range from backpacker to upscale tech company vibes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3iGBj5Ra_Opuq6sd" /><figcaption>Photo credit: Me, WeWork</figcaption></figure><h4>Selina</h4><p><a href="https://www.selina.com/colombia/medellin">Selina</a> is part of a hostel and has a grassroots feel with an affordable monthly rate compared to traditional office space. It contains both a cafe and a restaurant. Their weekly events like yoga, happy hours, live shows, etc., are a great time to meet people.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9LMUOynQxRQ7KNmw" /><figcaption>Photo credit: Me, Selina</figcaption></figure><h4>WeWork</h4><p><a href="https://www.wework.com/l/medellin">WeWork</a> feels like a Google office with cafes, amenities, stunning views, and a variety of work areas. After lunch, people found their favorite spot to close their eyes for an afternoon siesta.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5WshWRUpJ7KRYwBz" /><figcaption>Photo credit: Me, WeWork lobby</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sY9mym1Ia9vu3llr" /><figcaption>Photo credit: Me, WeWork lounge chairs on outside patio</figcaption></figure><p><em>See </em><a href="https://www.google.com/maps/@6.2184754,-75.6009813,13z/data=!4m3!11m2!2s18XtIu8OmB9X0pmeO2HbilOj97is!3e3"><em>Coworking map</em></a></p><h3>Communities</h3><p>There is most likely a community here for you based on your professional interest. I’ve met salespeople, marketers, designers, programmers, wellness professionals, etc. here.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*h4H_MTl2NxqkMxuU" /><figcaption>Photo credit: Ruhanna Rahman, Android Medellín talk on Unidirectional Flow pattern</figcaption></figure><p>Begin by searching on <a href="https://meetup.com/">Meetup.com</a>. I found the <a href="https://www.meetup.com/Medellin-Android/">Medellín Android</a> group. The majority of the first talk I attended was in Spanish. Once the event was over the organizers Cristián Gomez and <a href="https://twitter.com/cdmunozi">Carlos Daniel</a> introduced themselves to me with a smile.</p><p>After speaking with them I felt welcomed and they invited me to give my own talk. <a href="https://www.youtube.com/watch?v=Elp-Z-pQTpM">I gave a talk</a> there on <a href="https://docs.google.com/presentation/d/1bj-lG2ghJO5EVIJRrCt3eH-lZsz8ChPc1hVXZ_KYpD8/edit?rm=minimal#slide=id.p">Android architecture patterns</a>. The one feature I still actively use Facebook for is finding local groups to learn from by searching for “Digital nomads in {city}”.</p><h3>Healthy eating</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1pkRxKJ8PWoPg8Dm" /><figcaption>Photo credit: Me, Pardo’s grilled chicken salad</figcaption></figure><p>Street food is tasty. However, in order to feel good and be productive, eating healthy is important. Although more expensive, there are great options from around the world.</p><h3>Fast casual</h3><ul><li>Mediterranean options like <a href="https://goo.gl/maps/Ug2boRwBqQP2">Falafel Time</a></li><li><a href="https://goo.gl/maps/wkTGw3uKeWG2">German Street Food</a>’s salads</li><li><a href="https://goo.gl/maps/F9b5UpAaVR92">Tacohouse</a> chains: Identical to Chipotle in food and price</li><li><a href="https://goo.gl/maps/AcHgrzZBRqk">Saludpan</a>: Salads</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2jMgOo4vM0Uw2xg5" /><figcaption>Photo credit: Me, Ceres</figcaption></figure><h3>Sit down</h3><ul><li><a href="https://goo.gl/maps/ki4vDYoQzpB2">Burdo</a>: For lunch or drinks</li><li><a href="https://goo.gl/maps/UPwxS88z6vq">Pardo</a>: Cafe with healthy food options</li><li><a href="https://goo.gl/maps/FyvUeqYVtvo">Betty’s Bowls</a>: Build your own protein bowls</li><li><a href="https://goo.gl/maps/6jJtPY9kqHR2">Zaaika Indian</a>: Indian food</li><li>Bárbaro Cocina Primitiva (<a href="https://goo.gl/maps/yfofuc5YL8q">El Poblado</a>, <a href="https://goo.gl/maps/Qsn4HBBtSax">Laureles</a>): Great and affordable steaks</li><li><a href="https://goo.gl/maps/jazLJTUiePz">Mezzaluna</a>: Salads</li></ul><p><em>See</em> <a href="https://www.google.com/maps/@6.2240424,-75.5962344,14z/data=!4m3!11m2!2s1RKBqJ1peZoBkbJQ0SP3R0ijDH6g!3e3"><em>food maps</em></a></p><h3>If you’re interested in “Why Medellín?”, the best local flavors, and activities to experience, read the following.</h3><p><a href="https://adamhurwitz.medium.com/medellin-hidden-gem-2004ea0508ca">Medellín — The nomad’s hidden gem</a></p><p><a href="https://adamhurwitz.medium.com/medell%C3%ADn-a-taste-of-the-classics-and-the-best-cacao-e01cd71f7213">Medellín — A taste of classics and the best cacao</a></p><p><a href="https://adamhurwitz.medium.com/medellin-discover-the-local-appeal-43fd593e4336">Medellín — Discover the local appeal</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/234/0*mFb8TCqoJv3pxdRA" /></figure><p><a href="http://twitter.com/adamshurwitz"><em>Adam Hurwitz</em></a><em> enjoys sharing </em><a href="https://docs.google.com/document/d/17dD4YedWuiw-Q7GSv9WCC2rYfsLmLZoqqXzWitLhXlg/edit#heading=h.oxw1gj6buyn9"><em>travel open info</em></a><em> and is a researcher and product consultant.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*d-4EStolJaFH7T7T" /><figcaption>Photo credit: Ruhana Rahman</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f5a2201bfa44" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Medellín — Discover the local appeal]]></title>
            <link>https://adamhurwitz.medium.com/medellin-discover-the-local-appeal-43fd593e4336?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/43fd593e4336</guid>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[medellin]]></category>
            <category><![CDATA[latin-america]]></category>
            <category><![CDATA[nomad]]></category>
            <category><![CDATA[life-hacking]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Mon, 13 Nov 2023 05:06:23 GMT</pubDate>
            <atom:updated>2023-11-13T05:19:10.671Z</atom:updated>
            <content:encoded><![CDATA[<h3>Medellín — Discover the local appeal</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BxvSgBtrEmFKZ1Ww.png" /><figcaption>Photo credit: Me, mural in Comuna Thirteen neighborhood</figcaption></figure><p><em>Note: This was originally published on </em><a href="https://hackernoon.com/discovering-the-local-appeal-of-medellin"><em>HackerNoon</em></a><em> and written in 2019.</em></p><p>During his travels, author and podcaster <a href="https://tim.blog/">Tim Ferriss</a> mentioned on comedian <a href="https://pca.st/uq08">Ari Shaffir’s podcast</a> that the first thing he likes to do in a new city is to take a bike tour. I followed that move, taking a walking tour my first few days after arriving to get a sense of the areas, following that up with experiencing local marketplaces, bars, and nightlife.</p><p>I traveled to Medellín, Colombia, both to explore for a week on vacation and then to immerse myself in daily life, while working remotely as the creator of <a href="https://github.com/adam-hurwitz/Coinverse#coinverse-open-app">Coinverse</a>, the first cryptocurrency news audiocast app.</p><h3>Tours</h3><p>Whether your interest is history, marketplaces, art, or coffee, there is something for you. I have always avoided what I thought of as “traditional tours.” After experiencing RealCityTours “free” city walking tour, where you pay with a tip, it is an experience I will actively seek out on future trips. The free tour was led by a local who was an interactive storyteller.</p><p>It’s best to base your tours on personal recommendations and/or online reviews. I got suckered into a “transformation tour” through a popular hostel chain that was advertised as educational to learn about the positive evolution of neighborhoods.</p><p>In actuality, it was a drive to three different tourist spots to look around and spend money with the tour guide’s friends.</p><p>I’ve had a great experience and received excellent in-person feedback on the tours below.</p><ul><li><a href="https://www.freetour.com/">freetour.com</a></li><li><a href="http://www.realcitytours.com/">Real City Tours</a>: <a href="https://www.realcitytours.com/free-walking-tour/">Free walking tour</a>, <a href="https://www.realcitytours.com/exotic-fruits-tour/">Exotic fruits tour</a>, and <a href="https://www.realcitytours.com/barrio-transformation-tour/">Transformation tour</a></li><li><a href="http://www.comuna13tours.com/">Comuna 13 Medellin Graffiti Tour</a></li><li><a href="https://www.urbancoffeetour.com/">Urban coffee tour</a></li></ul><h3>Markets</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_zd7hmAyrooGp_AG" /><figcaption>Photo credit: Me, Mercado del Rio</figcaption></figure><p>Marketplaces refer to anything from “mom and pop” merchants where everyday people or businesses buy groceries, to hubs with restaurants from around the world. Both are worth exploring. The former lends itself to more adventure.</p><h4>Traditional Marketplaces</h4><ul><li><a href="https://goo.gl/maps/XfcM3AfnFdH2">Plaza Minorista</a></li><li><a href="https://goo.gl/maps/5JtaaKsKzb92">San Alejo</a> <em>1st Saturday of the month</em></li><li><a href="https://goo.gl/maps/oyUzxU6pEJu">San Fernando Plaza</a></li><li><a href="https://goo.gl/maps/pD4n5f2qrLH2">Market Square Envigado</a></li><li><a href="https://goo.gl/maps/wEjj5ydXQJD2">Central Mayorista de Antioquia</a></li><li><a href="https://goo.gl/maps/8ZnPJJCPB8t">Placita de Flórez</a></li><li><a href="https://www.facebook.com/elpulguerollanogrande/">El Pulguero Llanogrande</a> <em>Limited to a few times per year</em></li></ul><h4>Restaurant Hubs</h4><ul><li><a href="https://goo.gl/maps/T25TYASATtL2">Mercado del Río</a></li></ul><h4>Resources</h4><ul><li><a href="https://www.google.com/maps/@6.2228692,-75.5966207,13z/data=!4m3!11m2!2sMNnXrVj4aAp0D8f0c4ZiFX4O-_uc3w!3e3">Markets map</a></li><li><a href="https://theculturetrip.com/south-america/colombia/articles/the-best-markets-in-medellin-colombia/">The Best Markets in Medellín, Colombia</a> <em>by CultureTrip</em></li><li><a href="https://theculturetrip.com/south-america/colombia/articles/the-best-street-food-markets-in-medellin/">The Best Street Food Markets in Medellín</a> <em>by CultureTrip</em></li></ul><h3>Social scene</h3><p>Through the handful of nights I went out, I experienced the variety the city offers. I explored a battle of the bands at the <a href="https://www.selina.com/colombia/medellin/">Selina</a> hostel, danced to Reggaeton till 4 AM on my birthday at <a href="https://goo.gl/maps/H8xBVA9j2h6VWxyc9">Buena Vista</a>, stepped on a few toes at a local salsa joint, <a href="https://goo.gl/maps/8zbtCEt3ER9PTe9a7">El Tíbiri</a>, helped host a rooftop morning yoga and dance party, and saw a new friend DJ.</p><p>My social circle grew through roommates, informal events friends hosted, tech meetups, etc.</p><p>One of my favorite experiences was <a href="https://goo.gl/maps/myMcYByVu3rQsgAf7">Calle 66a</a>. A local friend introduced me to this street where you walk up to a bodega, drink beer, and hang out on the street. People are here every night, and I hear it can get rowdy into the early morning.</p><p>If looking for the hostel party scene, there are well-known “party hostels” like <a href="https://www.selina.com/colombia/medellin/">Selina</a> and the <a href="https://goo.gl/maps/i5UsCfvyHHP2">Purple Monkey</a>. For bars and clubs, the two main spots are around <a href="https://goo.gl/maps/y7Q6TUZbVD8FRfbv6">Parque Lleras</a> in El Poblado and <a href="https://goo.gl/maps/4wqweKCbxDxefNNm9">Carrera 70</a> in Laureles.</p><p>An alternative to the bar scene is language exchanges like <a href="https://goo.gl/maps/CiKinSL8wUE2">El Social</a> (Wednesdays), and <a href="https://www.meetup.com/meetup-group-DMGwjCgs/">Café Ondas</a> (Wednesdays and Fridays). Locals and travelers meet in order to practice speaking, listening, and hanging out.</p><h3>Outdoors</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DOf-gpZgBfCw5UvE.jpeg" /><figcaption>Photo credit: Ely Roa, Me in the Cuevas del Higueron waterfall, totally not cold at all</figcaption></figure><p>Within a quick trip from downtown, you can be in the jungle jumping into a waterfall or overlooking the cityscape. The proximity of the locations and range of difficulty in accessing these sites make the samples below easy to get to from downtown.</p><p>I’m happy I did <a href="https://theunconventionalroute.com/cuevas-del-higueron-el-salado/">Cuevas del Higuerón</a> last minute. I chose this because it was a decent workout, thanks to the ascent, a view of the city, and the opportunity to jump in the waterfall. The waterfall was cold, surprisingly not as cold as Yosemite National Park’s waterfall in California during the summertime.</p><p>You can take Uber or public transportation to the trailhead at <a href="https://goo.gl/maps/wjsZwZJS2oQ5JAnVA">Cuevas del Higuerón — Camino Ancestral — Camino Real La Ayurá</a>.</p><p>Next time I’d love to do El Cerro De Las Tres Cruces because of how easy it is to access and the citywide view. The hike up lasts only 30–45 min. To get to the trailhead, take an Uber to <a href="https://goo.gl/maps/AZs9kSyNWQiCsxRU8">Casa de los Bernal</a> which will take you to the start point.</p><p>Here are more great outdoor adventures.</p><ul><li><a href="https://www.facebook.com/larutatrekking/">La Ruta hiking Facebook group</a></li><li><a href="https://parquearvi.org/en/producto/arvi-by-bike/">Parque Arví bike</a></li><li><a href="https://parquearvi.org/Paginas/PageNotFoundError.aspx?requestUrl=https://parquearvi.org/en/producto/arvi-by-bike/">Parque Arví hike</a></li><li><a href="https://parquearvi.org/Paginas/PageNotFoundError.aspx?requestUrl=https://parquearvi.org/en/producto/arvi-by-bike/">Parque Arví night hike</a></li><li>ATV tour offered by Rango Hostel</li><li>Mountain bike descent offered by Rango Hostel</li></ul><h3>Spas</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QqAgCpmyAo4AJ461" /><figcaption>Photo credit: Me, Diez 10 Hotel rooftop</figcaption></figure><p>After a week of exploring, it is nice to wind down with a massage on the rooftop of the <a href="https://goo.gl/maps/sZT68vmT9h72">Diez 10 Hotel</a>. It took me a while to book the full body massage because I had a hard time understanding what I was ordering due to my Spanish, or lack thereof. The massage was good, costing $50 per hour, about half the cost compared to the states.</p><p>If you decide to go to Diez, the following descriptions of the menu will save you my confusion.</p><ul><li><a href="https://www.nirvanaspacolombia.com/compra-online/masaje-terrenal/">Masaje terrenal</a>: Full body using hot stones</li><li><a href="https://www.nirvanaspacolombia.com/compra-online/masaje-nuez-coco/">Masaja tropical / nuez de coco</a>: Using coconuts</li><li>Masaja de caña: Using bamboo</li><li><a href="https://www.nirvanaspacolombia.com/compra-online/masaje-atardecer-llanero/">Masaja atardecer llanero</a>: Using hot wax</li></ul><p>For a less fancy and highly rated experience, <a href="https://goo.gl/maps/c7rrXbtaZHYzNGWF9">Sports Massage</a> is $40 per hour.</p><h3>Lastly, I explore how to work effectively.</h3><p>See <a href="https://adamhurwitz.medium.com/medell%C3%ADn-work-remotely-f5a2201bfa44">Medellín — Work remotely</a></p><p>If you’re curious “Why Medellín?” or for the best local flavors, read the following.</p><p><a href="https://adamhurwitz.medium.com/medellin-hidden-gem-2004ea0508ca">Medellín — The nomad’s hidden gem</a></p><p><a href="https://adamhurwitz.medium.com/medell%C3%ADn-a-taste-of-the-classics-and-the-best-cacao-e01cd71f7213">Medellín — A taste of classics and the best cacao</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/234/0*EIso3jCyK0cCV_qH" /></figure><p><a href="http://twitter.com/adamshurwitz"><em>Adam Hurwitz</em></a><em> enjoys sharing </em><a href="https://docs.google.com/document/d/17dD4YedWuiw-Q7GSv9WCC2rYfsLmLZoqqXzWitLhXlg/edit#heading=h.oxw1gj6buyn9"><em>travel open info</em></a><em> and is a researcher and product consultant.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*yysVjmQ_VIzYVL6s" /><figcaption>Photo credit: Ruhana Rahman</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=43fd593e4336" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Medellín — A taste of the classics and the best cacao]]></title>
            <link>https://adamhurwitz.medium.com/medell%C3%ADn-a-taste-of-the-classics-and-the-best-cacao-e01cd71f7213?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/e01cd71f7213</guid>
            <category><![CDATA[medellin]]></category>
            <category><![CDATA[nomad]]></category>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[latin-america]]></category>
            <category><![CDATA[life-hacking]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Mon, 13 Nov 2023 05:06:09 GMT</pubDate>
            <atom:updated>2023-11-13T05:16:21.700Z</atom:updated>
            <content:encoded><![CDATA[<h3>Medellín — A taste of the classics and the best cacao</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vNBjD6ximdC7PaVt.png" /><figcaption>Photo credit: Me, Sopa de mondongo from <a href="https://goo.gl/maps/5KECvKHRPtt">Restaurante Mondongo’s El Poblado</a></figcaption></figure><p><em>Note: This was originally published on </em><a href="https://hackernoon.com/medellin-a-taste-of-the-classics-and-the-best-cacao"><em>HackerNoon</em></a><em> and written in 2019..</em></p><p><a href="https://duckduckgo.com/?t=ffab&amp;q=Anthony+Bourdain&amp;atb=v317-1&amp;ia=web">Anthony Bourdain</a> taught us there is no better way to explore a place than through people and food. I traveled to Medellín, Colombia, both to explore for a week on vacation and then to immerse myself in daily life while working remotely as the creator of <a href="https://github.com/adam-hurwitz/Coinverse#coinverse-open-app">Coinverse</a>, the first cryptocurrency news audiocast app.</p><p>Despite eating mostly <a href="https://medium.com/@AdamHurwitz/i-spent-30-days-eating-fatty-foods-809cea1d93b3">vegetables and protein</a>, it is important to experience the local cuisine. Most of my research was compiled at 3 A.M., thanks to the au pair in the adjacent seat on my red-eye who was also unable to sleep.</p><h3>Must try</h3><h4>Classics</h4><ul><li>Bandeja Paisa</li><li>Empanadas</li><li>Arepa Paisa</li><li>Sopas</li><li>Hot dogs</li></ul><h4>Empanadas</h4><p>They have a major difference from the Mexican variety, made from corn instead of flour, theoretically healthier if not deep-fried. Empanadas are available everywhere. The ones below came recommended by <a href="https://goo.gl/maps/ZhwPN5ufxvF2">Empanadas Envigadeñas</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DgiWvGYHmZzrVU9s.png" /></figure><h4>Sopa</h4><p><a href="https://en.wikipedia.org/wiki/Sopa_de_mondongo">Sopa de Mondongo</a> is moreso a hardy meal. I believed I was having chicken but later discovered that it was more ‘exotic’, the stomach of a cow or pig.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VmKAt62iBGQn5SNh.png" /></figure><p>Here are places to check out for classic stews.</p><ul><li><a href="https://www.google.com/maps/search/Asados+Do%C3%B1a+Rosa/@6.251834,-75.588863,14.07z?entry=ttu">Asados Doña Rosa</a></li><li><a href="https://goo.gl/maps/5KECvKHRPtt">Restaurante Mondongo’s El Poblado</a></li></ul><h4>Hot dogs</h4><p>“Hot dogs” is an understatement. Nothing about the Perro Malvada Pequeña was pequeña. After some cerveza and tasting Aguardiente (the national tequila), this was a fine idea. Alone and overwhelmed with the menu, I rolled the dice and told them I’d have what the person in front of me was having. This puts the Philly Cheesesteak to shame. Towards the weekend, this food stand is open at the intersection of <a href="https://goo.gl/maps/783Um7Yh87SmUY5p9">Carrera 43A and Calle 7D</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5UFyldg2L-snXs82.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*LL8lUV8slmxXCUPk.png" /></figure><h3>Chocolate</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1F_3qPseXIBghQyd.png" /></figure><p>I prefer chocolate with high amounts of cacao, which Medellín is known for. Below are a few of my favorite cacao-rich chocolates.</p><h3>Store-bought</h3><p>1. Lok, bought at <a href="https://goo.gl/maps/nM9b57B5e2cGfKxB9">Cacao Brunch</a>, from <a href="https://en.wikipedia.org/wiki/Arauca_Department">Arauca</a>. This is exactly what I had been looking for, pure 100% cacao. It contains an initial bitter punch, then a dark, smooth, and rich aftertaste. I bought sweeter chocolates for gifts and stocked up on Lok for myself.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NXIc5MF0sRYYWtEK.png" /></figure><p>2. Tilín Tilín, bought at <a href="https://goo.gl/maps/eT1UBkDPMSR3w2YQ9">Tilín Tilín Chocolate Factory</a>, from <a href="https://en.wikipedia.org/wiki/Tumaco">Tumaco</a>. This is my favorite gift for others. Each variety has a distinct flavor. Rooibos is one of my favorite teas containing sweetness, making the 69% cacao Rooibos &amp; Roses bar my top pick. Rooibos &amp; Roses provides instant sweetness with a hint of tart.</p><p>Pro-tip, the longer you talk with the chocolate tender, the more free samples you’ll be rewarded.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jkGTbu1IjuQcTSWr.png" /></figure><p>3. <a href="https://tibito.co/?lang=en">Tibitó</a>, bought at <a href="https://goo.gl/maps/PdUavzQgafYoeRJN7">Cacao brunch</a>, from <a href="https://en.wikipedia.org/wiki/Meta_Department">Meta</a>, <a href="https://en.wikipedia.org/wiki/Putumayo_Department">Putumayo</a>, Tumaco, and Arauca. All four are smooth and creamy, tasting similar to milk chocolate. I could not identify major flavor differences between each.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*a061wKzYSYEdBfj-.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6nQ-RRzfC07Hxbdk.png" /></figure><p>4. <a href="https://www.cacaohunters.com/collections/todos-nuestros-productos">Cacao Hunters</a>, bought at <a href="https://goo.gl/maps/TBQye4qnU1jZ6G837">Urbania Café</a>, from Tumaco. The 82% cacao bar is dark, smooth, and simple. Sold at one of my favorite cafes, Urbania pairs a sample of chocolate with their coffee.</p><p>5. Moctezuma, bought at Fruta Provenza, from <a href="https://en.wikipedia.org/wiki/Guatap%C3%A9">Guatapé</a>. This has a homemade feel, loosely wrapped in tin foil within the box. It was good, but it had no distinct flavor due to the overpowering of sweet jam at the bottom.</p><p>The store Fruta Provenza is fantastic, selling healthy food not found at most grocery stores. During my chocolate haul, I made an impulse purchase of almond flour baked bread.</p><p>6. Kalu, bought at Fruta Provenza, from <a href="https://en.wikipedia.org/wiki/Pereira,_Colombia">Pereira</a>. The flavor was mild and chocolate fairly smooth, but nothing stood out from the peanuts, raisins, blueberries, and coconut.</p><p>7. <a href="https://www.chocolatesjet.com/productos/">Jet</a>, bought at any grocery store, from <a href="https://en.wikipedia.org/wiki/Rionegro">Rionegro</a>. I could not find dark chocolate, but had to try the milk chocolate. It’s a classic from 1920, and what you’d expect from a standard milk chocolate without the depth of a Tilin Tilin or Tibitó.</p><p>I ran out of time to find <a href="https://suagu.com/categoria-producto/choc/">Chocolate Suagu</a>. According to their team, they are sold at <a href="https://goo.gl/maps/ukdNifi6Hkp17vBT7">Éxito de Envigado</a>, <a href="https://goo.gl/maps/B54tN24Z5QerMwwE9">Unicentro Shopping Center</a>, <a href="https://goo.gl/maps/7T8iXZdsnXUJzg5MA">Viva Laureles</a>, and at the Sol Verde in the <a href="https://goo.gl/maps/yqAQhPLrr6JyVmTq9">Oviedo mall</a>.</p><p>I could not find the following which have stores in Bogotá.</p><ul><li><a href="https://cacaoymascacao.com/">Cacao y Más Cacao</a></li><li><a href="http://chuculat.com/en/home/">Chuculat</a></li><li><a href="https://www.facebook.com/carlota.chocolat.3">Carlota Chocolat</a></li></ul><p><em>See </em><a href="https://theculturetrip.com/south-america/colombia/articles/7-colombian-chocolate-brands-you-need-to-know-about/"><em>7 Colombian Chocolate Brands You Need to Know About</em></a><em> by CultureTrip</em></p><h3>Ceremonial cacao</h3><p>The best cacao was at a private cacao ceremony I attended. Check with your health professional before trying as this cacao affects blood pressure. My roommates planned this experience, and I enthusiastically joined.</p><p>I only heard of cacao ceremonies at yoga studios and through a 2016 <a href="https://www.gizmodo.co.uk/2016/05/german-techno-enthusiasts-now-snort-chocolate-to-get-high/">story</a> of Berlin clubbers having cacao instead of alcohol and drugs. I envisioned sipping the hot drink in a relaxed setting, which it was, but it was also much more.</p><p>The master of ceremony was a nice woman named Sandy, who had studied these types of ceremonies throughout Latin and South America for many years. She informed us this ritual has been shared for thousands of years by the people across the Americas.</p><p>Before European conquests, cacao was used as currency and sacred medicine. Ceremonial cacao has a strict and specific selection process compared to store-bought cacao.</p><p>Beginning the first cup, I was warned to take my time. A few sips in, and I felt a strong sensation in the top left of my chest where a large vein was. I slowly continued feeling a strong yet relaxed buzz spread to my head and the rest of my body. The final effect was mental clarity and relaxation without any caffeine jitters.</p><p><em>See </em><a href="https://www.youtube.com/watch?v=IUWEOhF9HXg"><em>What is a cacao ceremony?</em></a><em> by USA Today</em></p><h3>Next up, I explore the local activities and how to work effectively.</h3><p><a href="https://adamhurwitz.medium.com/medellin-discover-the-local-appeal-43fd593e4336">Medellín — Discover the local appeal</a></p><p><a href="https://adamhurwitz.medium.com/medell%C3%ADn-work-remotely-f5a2201bfa44">Medellín — Work remotely</a></p><p>If you’re curious “Why Medellín?”, read <a href="https://medium.com/@adamhurwitz/medellin-hidden-gem-2004ea0508ca">Medellín — The nomad’s hidden gem</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/234/0*JMzKs9G-xLZvStcV" /></figure><p><a href="http://twitter.com/adamshurwitz"><em>Adam Hurwitz</em></a><em> enjoys sharing </em><a href="https://docs.google.com/document/d/17dD4YedWuiw-Q7GSv9WCC2rYfsLmLZoqqXzWitLhXlg/edit#heading=h.oxw1gj6buyn9"><em>travel open info</em></a><em> and is a researcher and product consultant.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*L-w1KrW_FD0_ZNcI.png" /><figcaption>Photo credit: Ruhana Rahman</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e01cd71f7213" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Medellín — The nomad’s hidden gem]]></title>
            <link>https://adamhurwitz.medium.com/medellin-hidden-gem-2004ea0508ca?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/2004ea0508ca</guid>
            <category><![CDATA[travel]]></category>
            <category><![CDATA[remote-work]]></category>
            <category><![CDATA[medellin]]></category>
            <category><![CDATA[life-hacking]]></category>
            <category><![CDATA[latin-america]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Mon, 13 Nov 2023 05:04:44 GMT</pubDate>
            <atom:updated>2023-11-13T05:08:38.668Z</atom:updated>
            <content:encoded><![CDATA[<h3>Medellín — The nomad’s hidden gem</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*SAqw8kWbIH-HtKQ4.png" /><figcaption>Photo credit: Ruhana Rahman, view from El Poblado rooftop looking southward</figcaption></figure><p><em>Note: This was originally published on </em><a href="https://hackernoon.com/medellin-the-nomads-hidden-gem"><em>HackerNoon</em></a><em> and written in 2019.</em></p><p>As I decide to enjoy deep breaths of the warm ‘eternal spring’ air after my all-nighter on the plane, I notice a man running towards me.</p><p>Before my first solo trip, my friend <a href="https://waj.io/">Waj</a>, a seasoned traveler, imparted upon me that the best part of travel is the people you meet. In Medellín, you feel welcomed by the people who live here, which becomes contagious amongst travelers, creating a small-town feel in an international city.</p><p>Due to my lack of sleep, or poor Spanish skills, I was waiting in the airport parking lot for my Uber. The man running is Juan, from Uber. I realize as Juan and I run through the parking that he has abandoned his car in the pick-up loop. In most cities, an Uber would drive away after a short wait, and an unattended car would be confiscated by police. Not here, Juan came to find me.</p><h3>Why here</h3><p>This marks my first solo adventure. My last trip abroad was with a group in Israel eight years ago. I read <a href="https://twitter.com/tferriss">Tim Ferriss’</a> <a href="https://fourhourworkweek.com/">The 4-Hour Workweek</a> (T4HWW) when I was 20. During a college job, standing in the weight room waiting to spot frat bros, I vigorously annotated T4HWW.</p><p>Long-term travel being possible and affordable while working was a mindset shift. Tim’s <a href="https://tim.blog/podcast/">podcasts</a> led me to <a href="https://twitter.com/rolfpotts">Rolf Pott’s</a> <a href="https://vagabonding.net/">Vagabonding</a>, a book with practical advice, anecdotes, and insight on viewing the world was the launch pad for my travel.</p><p>Being stressed with work leading up to the trip, I thought about a favorite stand-up comedian and <a href="https://pca.st/skeptictank">podcaster</a>, <a href="https://www.instagram.com/arishaffir">Ari Shaffir</a>, and his global stand-up adventures. I asked myself, ‘What would Ari do?’, allowing me to take the leap.</p><p>For reasons such as its accessibility from San Francisco, Medellín is a great choice for my first solo adventure out of the wealth of places I haven’t visited. My plan was to explore for a week, and then work remotely as the creator of <a href="https://github.com/adam-hurwitz/Coinverse#coinverse-open-app">Coinverse</a>, the first cryptocurrency news audiocast app.</p><h3>Reasons to come here</h3><h4>Balance of city and nature</h4><p>Sitting on the beach is great for a few days, but I prefer an active approach to exploring via running, hiking, eating, drinking, and wandering. Medellín is a balance of a vibrant city with unique culture and history, nested in the valley, between the mountains and rainforest.</p><p>Parts of the city, like El Poblado, contain natural greenery and flowing streams throughout the neighborhood, while hiking opportunities are only a short rideshare away.</p><h4>Culture</h4><p>Everyone I know who has traveled here shares how welcoming and joyful the people are. It’s inspiring how Medellín has transformed in the last two decades. There are many markets to explore for food, crafts, and art, as well as unique buildings spanning many time periods and outside influences.</p><h4>Tech</h4><p>Theoretically, I can work on Coinverse from anywhere. However, Medellín has growing nomad and local developer communities. These communities are easy to access with platforms such as <a href="https://www.meetup.com/">Meetup</a> and Facebook.</p><p>Once connected to a few like-minded individuals, you begin discovering other groups. For example, after working at <a href="https://www.selina.com/colombia/medellin/">Selina coworking</a> for a week, I was part of an informal lunch club with a diverse group of people.</p><h4>Affordability</h4><p>Medellín is relatively affordable compared to most cities in the US, especially San Francisco, where a meal in a restaurant may cost one-half to two-thirds more.</p><h3>The city of coincidences</h3><p>As Juan drove down into Medellín, he sensed my excitement as the panoramic views of the city emerged from the valley. Without asking, he stopped three times in order to take my picture.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*pKVCctklaoSf_qHP" /></figure><p>A less-than-rigid schedule leads to opportunities to make unique connections where you may meet someone and end up talking for hours. I planned my trip not knowing anyone in Medellín.</p><p>Right before leaving, I saw a post from someone I met twice, Mary Evans, looking for a roommate starting the day I left the hostels. This set the theme for the coincidences.</p><p>The second week, at <a href="https://goo.gl/maps/JaLFUVvy1MR2ysYG8">Urbania Café</a>, I saw a potentially familiar face. <a href="https://www.linkedin.com/in/dpankaew">Derek Pankaew</a> is the co-founder of one of <a href="https://www.kickstarter.com/projects/169915454/flexr-worlds-first-foldable-travel-friendly-pullup">my favorite Kickstarters</a>. After Derek caught me studying his website on my laptop, he introduced himself, and we had lunch, brainstorming ideas for hours.</p><p>Discovering how cryptocurrency is having real-world impacts is important to me. I had not found such projects online when a guest of the apartment arrived my second to last week. I mentioned the project I’ve been working on.</p><p>This led to my new friend teaching me about how Medellín is a major city for crypto, using <a href="https://www.dash.org/">Dash</a>, a project creating financial freedom for everyday people. As I continued to ask questions, we talked the whole day, and I was invited to dinner with a developer working on Dash, which led me to meet other people in the Dash community.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vx9QSq6b_NIh67_D" /></figure><p>Lastly, when shopping for pull-up rings, I was confused when Google led me to a residential building. Upon realizing I accidentally went to <a href="https://tienda-sportfitness.com/">Sports Fitness</a>’ corporate office rather than the store, I assumed they would send me away.</p><p>After communicating my desire to acquire $30 pull-up rings, we talked for over an hour, and I left with an offer I couldn’t refuse, free pull-up rings in trade for sharing e-commerce insights.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0I_ON4zpKGrOdlCG" /></figure><p>Originally, I planned to be in Medellín for a month and then ended up extending it to two and a half months. You’ll see why in my posts on the best food, local activities, and working remotely. Follow me to stay tuned.</p><h3>Next up, exploring the classic foods, activities, and how to work effectively.</h3><p><a href="https://adamhurwitz.medium.com/medell%C3%ADn-a-taste-of-the-classics-and-the-best-cacao-e01cd71f7213">Medellín — A taste of classics and the best cacao</a></p><p><a href="https://adamhurwitz.medium.com/medellin-discover-the-local-appeal-43fd593e4336">Medellín — Discover the local appeal</a></p><p><a href="https://adamhurwitz.medium.com/medell%C3%ADn-work-remotely-f5a2201bfa44">Medellín — Work remotely</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/234/0*E60zJ8R9VvfI0LiC" /></figure><p><a href="http://twitter.com/adamshurwitz"><em>Adam Hurwitz</em></a><em> enjoys sharing </em><a href="https://docs.google.com/document/d/17dD4YedWuiw-Q7GSv9WCC2rYfsLmLZoqqXzWitLhXlg/edit#heading=h.oxw1gj6buyn9"><em>travel open info</em></a><em> and is a researcher and product consultant.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2004ea0508ca" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[1inch is …Inflationary?]]></title>
            <link>https://adamhurwitz.medium.com/1inch-is-inflationary-796cd0825be?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/796cd0825be</guid>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[token]]></category>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[bitcoin]]></category>
            <category><![CDATA[defi]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Mon, 24 May 2021 18:10:20 GMT</pubDate>
            <atom:updated>2021-05-24T18:53:33.335Z</atom:updated>
            <content:encoded><![CDATA[<h3>1inch Network Exchange is …Inflationary?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dhOKtNSf0TGeXIKkOYtvJA.png" /><figcaption>Photo by<a href="https://unsplash.com/@purzlbaum?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText"> Claudio Schwarz | @purzlbaum</a> on<a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText"> Unsplash</a></figcaption></figure><p><em>Originally posted on </em><a href="https://hackernoon.com/1inch-is-inflationary-2b3d34pi"><em>Hacker Noon</em></a><em>.</em></p><p>With how volatile the crypto market is, in order to achieve success it’s vital to find projects that are positioned for long-term success. Given how useful 1inch is to the DeFi community and its potential, the goal of this is to understand the governance around minting new tokens, and not as a “gotcha”.</p><p>After a user on Reddit called out that there is a function in the smart contract allowing the team to create unlimited tokens, I became interested in looking at the max token supply of 1inch.</p><p>My interest increased when a moderator on 1inch’s Discord denied the existence of this feature in the code. Being skeptical that no one has had already looked at this given 1inch’s popularity and success, I reluctantly went traveling down the wormhole of learning how to deploy the code onto the testnet to see for myself.</p><h3>Unlimited 1INCH Token Supply</h3><p>What are the governance mechanisms around minting more tokens? This creates implications for many of the <a href="https://1inch.exchange/?ref=hackernoon.com">1inch.exchange</a> services that rely on the value of the 1INCH token if the token can be inflated just like fiat currency.</p><p>With often having one of the best values for swapping between ERC-20 tokens and the best selection of tokens, 1inch quickly gained popularity and value. In January 2021 the 1INCH token was worth ~$1.00 and grew over 300% to now in May 2021, even after this market cycle’s largest pullback yet.</p><p>Currently, <a href="https://defipulse.com/?ref=hackernoon.com">DeFi Pulse</a> has 1inch at the eighth-largest decentralized exchange (DEX) in terms of USD total value locked, right under ~$82m, down from $1.5b at the end of Q1 2021.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Khx0-BobRSZKAFkZ2zCCyA.jpeg" /><figcaption>DeFi Pulse</figcaption></figure><p>1inch offers more services than most standalone DEXs, from aggregating other DEX prices, offering earnings via staking in liquidity pools and liquidity mining, and rewarding users for holding 1inch’s governance tokens, 1INCH. This is where it becomes important to understand the 1INCH token and its maximum supply, or lack thereof.</p><p>As <a href="https://cointelegraph.com/news/defi-platform-1inch-releases-governance-and-utility-token?ref=hackernoon.com">Cointelegraph reported</a> on December 25, 2020, there is a claimed total token supply of 1.5b 1INCH. 1inch is built on top of OpenZeppelin’s set of Solidity smart contracts.</p><p>As smart contract developer Zachary Fogg (<a href="https://twitter.com/zfogg?ref=hackernoon.com">@zfogg</a>) explains, typically, tokens with a limited supply built with OpenZeppelin, call the <a href="https://docs.openzeppelin.com/contracts/2.x/api/ownership?ref=hackernoon.com#Ownable-renounceOwnership--">renounceOwnership</a> function after the initial token creation in order to limit the ability to create or mint more tokens.</p><p>On line <a href="https://etherscan.io/address/0x111111111117dc0aa78b770fa6a738034120c302?ref=hackernoon.com#code">1119 of 1nch’s smart contract</a>, there is an external “mint” function that allows for the creation of new tokens. This functionality is removed by calling the above renounceOwnership function. According to all of the <a href="https://bloxy.info/address/0x111111111117dC0aa78b770fA6A738034120C302?ref=hackernoon.com">functions called in 1inch’s smart contract</a>, renounceOwnership has never been called.</p><h3>Governance</h3><h4><strong>The main question is what is the governance for the 1inch Gnosis multisig wallet?</strong></h4><p>There is an <a href="https://bloxy.info/txs/events_sc/0x111111111117dc0aa78b770fa6a738034120c302?signature_id=1046&amp;ref=hackernoon.com">OwnershipTransferred event</a> to a Gnosis multisig <a href="https://etherscan.io/address/0x5e89f8d81c74e311458277ea1be3d3247c7cd7d1?ref=hackernoon.com">wallet</a>. This could be tied to a governance model around token creation. However, the 1inch team has not confirmed this via Discord, a <a href="https://www.reddit.com/r/1inch/comments/lm3nyf/what_is_the_purpose_of_the_mint_function_in/?ref=hackernoon.com">Reddit post</a>, and <a href="https://twitter.com/AdamSHurwitz/status/1363932866535653381?ref=hackernoon.com">Twitter post</a>.</p><p>An admin on the 1inch Discord denied the existence of the external “mint” function, but it is unclear their relationship to the 1inch core team. In addition, the question was posed to 1inch investor, Pantera Capital, during a conference call and via email, and their clarification is being awaited.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tdHuLQA3ha8lNN1FMbr84w.jpeg" /></figure><h3>Minting New Tokens</h3><p>Creating new 1INCH tokens is tested by deploying the 1INCH smart contract on the Ethereum Ropsten testnet. First, the initial <a href="https://ropsten.etherscan.io/tx/0xc41cb73696bca70d1e663d1fefc326dba68bd22f2a1ba29aea68c6a438104cba?ref=hackernoon.com">1.5b 1INCH tokens are created</a>. Then, using the external mint function, we can create however many new tokens are desired. In this case, <a href="https://ropsten.etherscan.io/tx/0xacd653790cb0e5b8b6f24a065f319f485df09b485b7ec3d91f495fed39d9f5d2?ref=hackernoon.com">3b new 1INCH tokens are created</a>.</p><p>With a compelling product offering many services, it will be great to understand if there is a reasonable explanation in regards to the new creation of 1INCH tokens.</p><p>The best-case scenario is that there is a governance system set up around the Gnosis multisig wallet and that this system defines when and how new tokens are created.</p><p><strong>Follow me on </strong><a href="https://twitter.com/adamshurwitz?ref=hackernoon.com"><strong>Twitter</strong></a><strong> to stay connected.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0qukOdAxCe_EnpapiMKz1g.png" /></figure><p>(<strong><em>Disclosure</em></strong><em>: I own a small amount of 1INCH tokens</em>)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=796cd0825be" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Android Model-View-Intent with Unit Tests]]></title>
            <link>https://proandroiddev.com/android-model-view-intent-with-unit-tests-260e9a0cdd64?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/260e9a0cdd64</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[androiddev]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Mon, 05 Oct 2020 13:13:50 GMT</pubDate>
            <atom:updated>2020-10-10T01:26:25.130Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BZPEsn31GYpPvmYuLOJImg.png" /><figcaption>Photo by <a href="https://unsplash.com/@_zachreiner_?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Zach Reiner</a> on <a href="https://unsplash.com/@ahurwitz/likes?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4>Local unit testing using JUnit 5 and MockK</h4><p><strong>You’ve spent too much time testing if you have had to reconfigure a feature’s code to make it testable or have debugged the ordering of actions/events and the resulting test output. Using the Model-View-Intent pattern (MVI) provides the ability to test built-in to the pattern following the best practice of Test Driven Development (TDD).</strong></p><p>If you’re not familiar with MVI, the pattern structures how information is shared and how views are created and updated. MVI defines a clear contract between the view and business logic with an interface that is a one-way, immutable, unidirectional data flow (UDF) pattern. The structure of MVI grants the ability to implement with any reactive framework, such as Kotlin Coroutines, RxJava, and LiveData.</p><p>The last post<em>, </em><a href="https://proandroiddev.com/android-model-view-intent-with-kotlin-flow-ca5945316ec"><strong><em>Android Model-View-Intent with Kotlin Flow</em></strong></a><em>,</em> provides a comprehensive overview of the MVI pattern used in the Coinverse cryptocurrency news app<em>. </em>It compares MVI to a forest transferring information via its’ highly structured fungi network. If MVI 🌲 ️is like an intelligently designed forest’s ecosystem, then the unit tests 🐝 are the <a href="https://www.npr.org/sections/thesalt/2015/10/09/446928755/could-a-mushroom-save-the-honeybee"><em>insects that feed off the fungi network</em></a> and know whether it is poisonous, or in our case, contains errors.</p><p><em>Coinverse code</em></p><p><a href="https://github.com/AdamSHurwitz/Coinverse#coinverse-open-app">AdamSHurwitz/Coinverse</a></p><p><em>Also available on the</em><a href="https://play.google.com/store/apps/details?id=app.coinverse"><em> Play Store</em></a><em>.</em></p><h3>Overview</h3><p><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-unit-tests-260e9a0cdd64#1474"><strong><em>Advantages</em></strong></a></p><p><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-unit-tests-260e9a0cdd64#e08f"><strong><em>Implement</em></strong></a></p><p><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-unit-tests-260e9a0cdd64#1331"><strong><em>Summary</em></strong></a></p><h3>Advantages</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pHomGU2hYHtrM31Ngu6w-g.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@wolfgang_hasselmann?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Wolfgang Hasselmann</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4>🙋🏻 Why Local Unit Tests?</h4><p>Local unit tests with JUnit 5 can be run quickly prior to writing a feature and regularly to ensure code quality and mitigate regressions of features’ business logic. These tests do not require dependencies on the Android system. This covers the majority of code for most apps making it a great first step for testing. These local unit tests can be expanded into integration tests for more thorough testing of the data layer, such as network requests and local databases, and for instrumented tests including the UI components.</p><h4>🥼 Built-in testing</h4><p>Once modularized properly, the business logic model, in this case Android’s ViewModel component, and the view interface contract used in production are also used for local unit tests without modification to test the given intents that the model uses to generate the expected view states.</p><p>MVI defines the intents driven by the Android system and user actions that the model processes to create the view states. Because the ViewModel business logic component observes the intent actions and creates the final persisted data of each view state, a local unit test makes use of the existing ViewModel and view interface contract.</p><h4>🏎️ More efficient code</h4><p>Creating intents and view states with MVI reduced the test code by more than 50% compared to the previous implementation using the UDF and emitting many streams of data simultaneously.</p><p>100% of <em>FeedViewModel.kt</em> is covered in local unit tests in Coinverse with MVI comprising 306 lines of code in the <em>FeedViewTest.kt </em>class, compared to 753 lines of code across four test classes covering mostly the same code prior to the MVI refactor. In the pre-MVI pattern, UDF was implemented to separate the view, business logic, and data layer. However, many sources of data were emitted from the ViewModel to the View making the tests harder to read, write, and created issues with the order of data being emitted. By knowing exactly what view state should be emitted based on each intent with MVI, it makes the tests more reliable, quicker to build, and more concise.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v-i3XUsfmCHKkNwi0U7-2A.png" /><figcaption>FeedViewModel.kt Test Coverage</figcaption></figure><h3>Implement</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*01A7dTg55jv98MVmjyxHJw.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@fedictm?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Fredrik Ivansson</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4>📚 <strong>Librarie</strong>s — Step 1 of 5</h4><ul><li><strong>JUnit 5 with parameterized tests —</strong> Reuse the tests with a stream of many test cases.</li><li><strong>MockK —</strong> Creates components involving the Android system or classes handling data requests like a repository or database, returning data that would be expected to be returned in the loading, success, and error states.</li><li><strong>Kotlin coroutines — </strong>Initiate intent actions and observe view state changes with StateFlow.</li><li><strong>AssertJ —</strong> Evaluates the actual vs. expected states.</li></ul><p><em>build.gradle (Module)</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/985244ac35cadd98083302d275e53e81/href">https://medium.com/media/985244ac35cadd98083302d275e53e81/href</a></iframe><p><em>build.gradle (App)</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5f72e60ad830c7e0c97532ceb9c8444c/href">https://medium.com/media/5f72e60ad830c7e0c97532ceb9c8444c/href</a></iframe><p><strong>Releases/Versions</strong></p><ul><li><a href="https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine">JUnit 5</a></li><li><a href="https://github.com/mockk/mockk/releases">MockK</a></li><li><a href="https://github.com/Kotlin/kotlinx.coroutines/releases/">Coroutines</a></li><li><a href="https://mvnrepository.com/artifact/org.assertj/assertj-core">AssertJ</a></li></ul><h4>💼 ViewModel — Step 2 of 5</h4><p><strong>A. Define how components are injected</strong></p><p>In order to use the same business logic in production and testing, all components that rely on the Android system or data requests are injected into the ViewModel so that test instances can be used in the local unit tests.</p><p><em>See: Android Model-View-Intent with Kotlin Flow </em>&gt; <a href="https://proandroiddev.com/android-model-view-intent-with-kotlin-flow-ca5945316ec#0ad4"><em>Model — ViewModel</em></a><strong><em> &gt; ‘</em></strong><em>CoroutineScope and CoroutineDispatcher’</em><strong><em> </em></strong>and ‘<em>Dagger 2 Dependency Injection for ViewModels with AssistedInject’</em></p><p><a href="https://gist.github.com/AdamSHurwitz/b6cbc8da62ed8cffdee94074b81b1684#file-feedviewmodel-kt"><em>FeedViewModel.kt</em></a></p><ol><li>CoroutineScope — With the custom extension function,<a href="https://gist.github.com/AdamSHurwitz/964a37c60161d74b80cb8a1e520f2bef?L3#file-viewmodelutils-kt-L94-L103"><em>getViewModelScope</em></a><em>, </em>for production no scope is injected and the Android’s viewModelScope is used by default. In testing the TestCoroutineScope is explicitly injected.</li><li>FeedRepository — Data requests made to the network and local database will be mocked requiring a test instance of the repository to be injected.</li></ol><p><strong>B. Create the test components to inject</strong></p><p><em>FeedViewTestExtension.kt</em></p><ol><li>TestCoroutineDispatcher and TestCoroutineScope are created in a JUnit 5 test extension. The dispatcher is necessary to ensure coroutines run on a test thread and the scope defines the lifecycle that the coroutines runs within.</li><li>The ArchTaskExecutor is required for LiveData to return the PagedList.</li></ol><p><em>See: Android Unidirectional Data Flow — Local Unit Testing</em> <em>&gt; </em><a href="https://proandroiddev.com/android-unidirectional-data-flow-local-unit-testing-487a6e6f5c9#727b"><em>1a. Configuration — Extensions</em></a></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/88e7d919184800f894b104b3d3c769b3/href">https://medium.com/media/88e7d919184800f894b104b3d3c769b3/href</a></iframe><p><em>FeedViewTest.kt</em></p><ol><li>The mock repository is initialized and passed into the ViewModel along with the TestCoroutineScope created in <em>FeedViewTestExtension.kt </em>above.</li><li>The repository method getMainFeedNetwork&#39;s response is mocked to return test data. In the full sample code this emits a Flow of PagedList data. With the <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-overview"><em>Paging 3</em></a><em> </em>library this can be simplified removing the dependency to mock LiveData asPagedList and return a PagingData response with Kotlin coroutines. <em>See StackOverflow for Paging 2: </em><a href="https://stackoverflow.com/questions/50435770/how-do-i-create-a-pagedlist-of-an-object-for-tests/57794102#57794102"><em>Convert List Into PagedList With Mock DataSource.Factory</em></a></li><li>The FeedViewTestCaseStream is a Stream.of(...) FeedViewTestCase defined in a data class and represents different scenarios such as loading, success, and error states. The stream is passed into the test making the test reusable for all of the states defined.</li></ol><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6fd71a4aea54e11631bd0e0a9de25b16/href">https://medium.com/media/6fd71a4aea54e11631bd0e0a9de25b16/href</a></iframe><h4>🌉 Interface — Step 3 of 5</h4><p><strong>Bind the view interface contract to the ViewModel</strong></p><ol><li>The majority of logic tested is defined by implementing the view interface and binding it to the ViewModel. The interface represents the intent/actions sent from the view to the ViewModel, and the view states rendered by the ViewModel back to the view. <em>See: Android Model-View-Intent with Kotlin Flow &gt; </em><a href="https://proandroiddev.com/android-model-view-intent-with-kotlin-flow-ca5945316ec#ee30"><em>Intent — View Interface</em></a></li><li>The view’s StateFlow intents are defined in FeedViewIntent for organizational purposes. <em>See: Android Model-View-Intent with Kotlin Flow &gt; </em><a href="https://proandroiddev.com/android-model-view-intent-with-kotlin-flow-ca5945316ec#558f"><em>View — Activity/Fragment</em></a></li><li>The test view interface observes emissions from the intent Flows and the test view interface renders a view state sealed class as it would in production.</li></ol><p><em>FeedViewTest.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a6f5a73a8cf45f7846199b57793b7cb5/href">https://medium.com/media/a6f5a73a8cf45f7846199b57793b7cb5/href</a></iframe><h4>🖼️ Intents and View States — Step 4 of 5</h4><ol><li>Call the intents/actions that initiate the business logic to generate a view state, in this case, loadFromNetwork intent is emitting a true value which initiates the FeedViewModel to create the Feed state. Then, observe that the actual view state of type Feed matches the expected state.</li><li>isEqualToComparingFieldByField is used to compare the properties of the actual and observed Feed view state emitted. Feed is a class rather than a data class so that each instance of Feed has a unique hash code and can be properly emitted by the model.</li></ol><p><em>FeedViewTest.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b85b2dd77808ad57c0f87069fc026c69/href">https://medium.com/media/b85b2dd77808ad57c0f87069fc026c69/href</a></iframe><h4>🔬 Verify and Confirm Method Calls — Step 5 of 5</h4><ol><li>The MockK verification coVerify confirms that the method getMainFeedNetwork is called within the test.</li><li>confirmVerified checks that all method calls in the test are verified/expected to be called.</li></ol><p><em>See: Android Unidirectional Data Flow — Local Unit Testing</em> &gt; <a href="https://proandroiddev.com/android-unidirectional-data-flow-local-unit-testing-487a6e6f5c9#f476"><em>5. Verify Tests</em></a></p><p><em>FeedViewTest.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0564027a747b93bac961eabb6abe23c0/href">https://medium.com/media/0564027a747b93bac961eabb6abe23c0/href</a></iframe><h3>Summary</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q7VeGY_P0Od19ccJkZWe9w.png" /><figcaption>Photo by <a href="https://unsplash.com/@alienaperture?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Michael D</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4>⚠️ UDF Without MVI</h4><p>From<strong> Step 3 of 5 — Interface</strong> section above when a view state is rendered, we can see how much more complicated this is compared to UDF emitting many data types simultaneously. In this case, loading the feed data is done by observing two emissions of data, the view state, and a one-time view effect to update ads. This also creates ugly code nesting, and is harder to read vs. creating one view state with all of the view’s final persisted data contained.</p><p><em>See: Android Unidirectional Data Flow — Local Unit Testing &gt; </em><a href="https://proandroiddev.com/android-unidirectional-data-flow-local-unit-testing-487a6e6f5c9#8498"><em>4. Make Assertions</em></a></p><p><em>FeedLoadContentTests.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fb36f82c3604f68151cac10f100567f0/href">https://medium.com/media/fb36f82c3604f68151cac10f100567f0/href</a></iframe><h4>🌊 Alternatives to Kotlin Flow</h4><p>The Kotlin Flow implementation is powerful and concise in handling threading, lifecycle scope, and allowing for mutable and immutable flows.</p><p>MVI also works well for testing with other reactive frameworks as well.</p><ul><li>RxJava’s <a href="https://proandroiddev.com/rxjava-different-types-of-subjects-ef9183b5e87e"><em>Subject</em></a><em> </em>or<em> </em><a href="https://github.com/JakeWharton/RxRelay#rxrelay"><em>Relay</em></a> are also solid options for MVI in the data, view state, and view layers.</li><li>LiveData can be used to achieve the tests above for the view state layer using MutableLiveData and LiveData. It is not recommended for the data request repository layer as LiveData is tied to a view’s lifecycle scope and some data requests may need to be decoupled from the UI.</li><li>Callbacks could work in theory, but run the risk of becoming overly complicated with both the view and the ViewModel implementing listeners to send actions from the view to the ViewModel and rendering view states from the ViewModel to the view.</li></ul><p>Whichever tool is chosen, the MVI pattern separates the view, business logic, and data requests as well as modularizes the various features into view states make initiating the user and system actions and testing the generated view states from the model accessible.</p><h4>🌱 Next Steps</h4><p>Now that local unit tests are created for the business logic using the MVI pattern, there is a foundation to expand upon for integration and instrumented tests.</p><ul><li><strong>Integration</strong> — Components the app is dependent upon such as network requests and local databases. Retrofit requests can be tested using <a href="https://github.com/square/okhttp/tree/master/mockwebserver#mockwebserver">MockWebServer</a> and a Room database can be tested by creating a database with a <a href="https://docs.google.com/document/d/1pKyOklYCHGW5uthg6ZHzBCPGSzjv7cJeBjRCfn5Eqt8/edit#bookmark=id.5efsqsdsnm4">test app context using Robolectric</a>.</li><li><strong>Instrumented</strong> — End-to-end behavior of a real device or emulator. UI tests can be incorporated with <a href="https://developer.android.com/training/testing/espresso">Espresso</a>.</li></ul><p>Explore <a href="https://play.google.com/store/apps/details?id=app.coinverse"><em>Coinverse</em></a> on the Google Play Store or the <a href="https://github.com/AdamSHurwitz/Coinverse#coinverse-open-app"><em>open-source</em></a> code. <strong>Please</strong> <strong>clap</strong> if you like the above. The longer the clap button is pressed the more claps. 👏🏻</p><p><strong>📝 Notes</strong></p><ul><li><a href="https://docs.google.com/document/d/1vmOpCjw7MekFwD5ACNlByE69kYdMBXuzr-AViO79s2Y/edit#heading=h.fqmybit6z3iu"><em>MockK</em></a></li><li><a href="https://docs.google.com/document/d/1pKyOklYCHGW5uthg6ZHzBCPGSzjv7cJeBjRCfn5Eqt8/edit#heading=h.4xgqspndufnv"><em>JUnit Tests</em></a></li><li><a href="https://docs.google.com/document/d/13fmrGJbGHNEPo3FN7IDmwQjxjXK3qoACSRqjyawV32k/edit#heading=h.i0i19kug6lgk"><em>Model-View-Intent</em></a></li></ul><h4>I’m Adam Hurwitz, writer of code and more. Follow me on <a href="https://twitter.com/AdamSHurwitz"><em>Twitter</em></a>.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W9V1h_Y0m2693AtU3wbXDQ.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=260e9a0cdd64" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/android-model-view-intent-with-unit-tests-260e9a0cdd64">Android Model-View-Intent with Unit Tests</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Android Model-View-Intent with Kotlin Flow]]></title>
            <link>https://proandroiddev.com/android-model-view-intent-with-kotlin-flow-ca5945316ec?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/ca5945316ec</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[android-app-development]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Tue, 21 Jul 2020 08:50:26 GMT</pubDate>
            <atom:updated>2020-08-11T05:07:49.458Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BYYCHjnV-Ro7gY3cqZDcTA.png" /><figcaption>Photo by <a href="https://unsplash.com/@_zachreiner_?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Zach Reiner</a> on <a href="https://unsplash.com/@ahurwitz/likes?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4>Update from Unidirectional Data Flow with LiveData</h4><p><strong>You may think of ‘magic mushrooms’ when you hear ‘fungi network’. However, just like the Model-View-Intent (MVI) pattern, the forest’s organized fungi network of a connected info sharing system, like Model-View-Intent’s pattern, is indeed not magic.</strong></p><p><strong>The Unidirectional Data Flow (UDF) pattern does a great job to ensure information is sent in one direction from the view to the business logic, is retrieved, and processed in order to build the view. UDF alone does not organize how the data processed. The Model-View-Intent pattern structures the design of <em>how</em> information is shared and views are created. MVI defines a clear contract with the view and business logic using UDF. MVI</strong>🌲 ️<strong>is like an intelligently designed forest’s ecosystem vs. a one-way wave of info and events with UDF </strong>🌊 <strong>used</strong> <strong>by itself.</strong></p><p><em>Coinverse code</em></p><p><a href="https://github.com/AdamSHurwitz/Coinverse">AdamSHurwitz/Coinverse</a></p><p><em>Also available on the </em><a href="https://play.google.com/store/apps/details?id=app.coinverse"><em>Play Store</em></a><em>.</em></p><h3>Overview</h3><p><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-kotlin-flow-ca5945316ec#dcc7"><strong><em>Advantages</em></strong></a></p><p><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-kotlin-flow-ca5945316ec#d73b"><strong><em>Components</em></strong></a></p><p><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-kotlin-flow-ca5945316ec#4b09"><strong><em>Implement with Kotlin Flow</em></strong></a></p><ul><li><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-kotlin-flow-ca5945316ec#0ad4"><em>Model — ViewModel</em></a></li><li><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-kotlin-flow-ca5945316ec#558f"><em>View —Activity/Fragment</em></a></li><li><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-kotlin-flow-ca5945316ec#ee30"><em>Intent — View Interface</em></a></li></ul><p><a href="https://proandroiddev.com/android-model-view-intent-with-kotlin-flow-ca5945316ec#bbd3"><strong><em>Alternatives</em></strong></a></p><p><a href="https://medium.com/@AdamHurwitz/android-model-view-intent-with-kotlin-flow-ca5945316ec#fdb4"><strong><em>Summary</em></strong></a></p><h3>Advantages</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rsn9B9Y_vwDiXs9Ob1X1aw.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@brandgreen?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Brandon Green</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4>UDF</h4><p>By itself, the Unidirectional Data Flow provides organization and performance benefits.</p><ul><li>🌊 <strong>Linear flow —</strong> View initiates events from the user and system to generate and return immutable data.</li><li>📡 <strong>Async event control</strong> — Set the lifecycle and threading for the creation of the view data in the business logic.</li><li>💼 <strong>Reusability</strong> — Utilize common business logic across multiple views.</li></ul><h4>MVI + UDF</h4><p>Model-View-Intent builds upon Unidirectional Data Flow, better organizing the UI and business logic into view states and creating a clear contract between the UI and business logic with an interface.</p><ul><li>🖼️ <strong>UI modularization</strong> — Define the final UI results emitted and rendered in an interface contract and view state.</li><li>🏗 ️<strong>Scaling </strong>— Reuse code with clear organization.</li><li>🥼<strong>Unit test simply</strong> —Build tests by observing the existing view state results from the business logic.</li><li>🐛 <strong>Debug quicker</strong>— Examine the current view state emitted to determine the root cause.</li></ul><h4>Kotlin Flow</h4><ul><li>🏎️ <strong>Quick setup</strong> — Requires fewer libraries compared to Rx.</li><li>♻️ <strong>Lifecycle management</strong> — Inherently controls the lifecycle of the operation upon launch.</li><li>🧵 <strong>Threading management</strong> — Can be handled in one place when the flow is launched rather than each time data is observed. Additional customization can be added if needed.</li><li>👻 <strong>Avoid nesting nightmares</strong> — The callback style pattern is harder to work with and creates the need to initialize instance variables within a function to retrieve information from within nested logic.</li><li>🎨 <strong>Customization — </strong>The core c lasses, methods, and inherited classes and interfaces are clearly readable making it possible to edit or add functionality with extension functions.</li><li>🚦 <strong>Ease-of-use — </strong>It is easy to learn new topics via reading the good ol’ <a href="https://kotlinlang.org/docs/reference/android-overview.html">documentation</a>. The docs provide interactive examples.</li></ul><h3>Components</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cz3twxoSNLKV_85zuSD1QA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@__elisa__?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Elisa Kerschbaumer</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>By implementing Model-View-Intent with immutable intents and observing immutable state data, the architecture takes advantage of a Unidirectional Data Flow pattern.</p><p><em>Note: ‘</em>Intent’ is used as a naming convention for events or actions in an app and is not the same as Android system level <a href="https://developer.android.com/guide/components/intents-filters">Intents</a>.</p><h4>Model</h4><ul><li>Implement with a ViewModel, also referred to as a Presenter in other implementations.</li><li>Observes the requests for new actions or data requests from incoming intents and processes the business logic from the intent requests</li><li>Creates the view states</li><li>Stores the view state data on lifecycle changes</li><li>Emits an immutable state</li></ul><h4>View</h4><ul><li>Implement with an activity/fragment.</li><li>Displays the view state</li><li>Initiates events/actions by the user or the Android system</li></ul><h4>Intent</h4><ul><li>Implement with a view interface.</li><li>Defines the contract between the UI and business logic</li><li>Creates the immutable intents that return an observable object from the view to the ViewModel</li><li>Creates the method(s) to render the view state(s) from the ViewModel to the view</li></ul><h3>Implement with Kotlin Flow</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tv0cnNeynUA81pyBIBEdaw.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@naszymokiem?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Łukasz Maźnica</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>Model-View-Intent can be implemented with any reactive framework. <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/index.html">StateFlow</a> and <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/index.html">MutableStateFlow</a>’s introduction in Kotlin coroutines’ <a href="https://github.com/Kotlin/kotlinx.coroutines/releases/tag/1.3.6">1.3.6 release</a> makes Flow a fantastic framework from the view to the repository level. Mutable data can be updated privately as instance variables within a class and observed publicly as an immutable value.</p><p>Add the <a href="https://github.com/Kotlin/kotlinx.coroutines/tree/master#android"><em>kotlinx.coroutines</em></a><em> </em>library with the <a href="https://github.com/Kotlin/kotlinx.coroutines/releases/">latest version</a> to <em>build.gradle</em>.</p><h4>Model — ViewModel</h4><p><em>FeedViewState.kt</em></p><ol><li>A <a href="https://kotlinlang.org/docs/reference/sealed-classes.html"><em>Sealed</em></a><em> </em>class defines the view state group FeedViewState</li><li>Each UI module in the view state, Feed and OpenContent are defined as a sub-class. If there is a state that emits the same data values, but represents a unique event, use a class as a data class will consider these classes’ hash code equivalent, and not emit the state. For example, when the same info can be returned more than once consecutively.</li></ol><p>To re-use the ViewModel for more advanced use cases, the ViewModel can handle multiple types of sealed class view states.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d771d6f4fe32d164fd16d4f19cae3458/href">https://medium.com/media/d771d6f4fe32d164fd16d4f19cae3458/href</a></iframe><p><em>FeedViewModel.kt</em></p><ol><li>The view state is a private MutableStateFlow. Values can only be added from the ViewModel ensuring only the ViewModel can create/update the state.</li><li>The FeedView defines the intents and the contract between the model and view. It is passed into the ViewModel through a public function bindIntents that initiates observing all of the view intents. The intent Flows loadFromNetwork and selectContent are observed with onEach or the custom extension method onEachEvent for one-time events, described below.</li><li>The state is created or updated based on the <a href="https://github.com/android/architecture-components-samples/blob/88747993139224a4bb6dbe985adf652d557de621/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt"><em>Resource.kt</em></a> status LOADING, SUCCESS, and ERROR of the data request.</li><li>The view state changes are observed in the initState intent Flow and sent to the Fragment via the FeedView interface render function.</li></ol><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7548ecf9383212fccf401ffae893865f/href">https://medium.com/media/7548ecf9383212fccf401ffae893865f/href</a></iframe><p><strong>One-time Events</strong></p><p>One-time events don’t persist and include navigation, dialogs, or errors. To handle this with Flow, an <a href="https://gist.github.com/AdamSHurwitz/c4e50c771f8849d382f383e74abceeaa#file-event-kt"><em>Event.kt</em></a> wraps one-time Flows and is emitted with the custom extension function <a href="https://gist.github.com/AdamSHurwitz/c4e50c771f8849d382f383e74abceeaa#file-oneachevent-kt"><em>onEachEvent</em></a>. <a href="https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150"><em>LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)</em></a><em> </em>by <a href="https://medium.com/u/e0a4c9469bb5"><em>Jose Alcérreca</em></a><em> </em>explains this issue well in a discussion about LiveData. The event concept applies to all reactive frameworks.</p><p><strong>CoroutineScope and CoroutineDispatcher</strong></p><p>For JUnit tests, it is important to manually pass the CoroutineScope into the ViewModel and use the custom <a href="https://gist.github.com/AdamSHurwitz/964a37c60161d74b80cb8a1e520f2bef?L3#file-viewmodelutils-kt-L94-L103"><em>getViewModelScope</em></a><em> </em>function to return the appropriate scope inside the ViewModel.</p><p>The same thing goes for the CoroutineDispatcher which is not used in this example but can be implemented with <a href="https://medium.com/u/e70c655d45e1"><em>Craig Russell</em></a>’s <a href="https://craigrussell.io/2019/11/unit-testing-coroutine-suspend-functions-using-testcoroutinedispatcher#dispatcherprovider"><em>DispatcherProvider</em></a> included in <a href="https://gist.github.com/AdamSHurwitz/964a37c60161d74b80cb8a1e520f2bef#file-viewmodelutils-kt-L105-L119"><em>ViewModelUtils.kt</em></a>.</p><p><strong>Dagger 2 Dependency Injection for ViewModels with AssistedInject</strong></p><p><a href="https://medium.com/u/7a8d96da8cb6"><em>Gabor Varadi</em></a><em>’s </em><a href="https://proandroiddev.com/dagger-tips-leveraging-assistedinjection-to-inject-viewmodels-with-savedstatehandle-and-93fe009ad874#7919"><em>Dagger Tips</em></a><em> </em>article provides a clear implementation guide. Moving forward, <a href="https://developer.android.com/training/dependency-injection/hilt-android"><em>Dagger Hilt</em></a><em> </em>will simplify ViewModel injection once released in production.</p><h4>View — Activity/Fragment</h4><p><em>FeedViewIntent.kt</em></p><p>Private mutable intents can be initialized directly in the view or abstracted to a class like in <em>FeedViewIntent.kt </em>that defines the intents and data classes used<em>.</em></p><ol><li>Intents the Fragment will use to pass Flow data to the ViewModel through the FeedView interface.</li><li>Data classes the intents use to pass info.</li></ol><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6705fc5169d92e1e290bc27e93519d78/href">https://medium.com/media/6705fc5169d92e1e290bc27e93519d78/href</a></iframe><p><em>FeedFragment.kt</em></p><ol><li>Initialize the mutable intents stored in FeedViewIntent.</li><li>Bind the FeedView interface to the ViewModel in onViewCreated to begin observing the intents defined and emitted from FeedView.</li><li>Implement the functions defined in FeedView that emit Flow data to the ViewModel by overriding the interface functions.</li><li>Emit Flow data intents. a)initState returns a true Flow by default as it is responsible for binding all of the view states in the ViewModel when the view is created. b) loadFromNetwork occurs when the view is created for the first time. c) selectContent emits when an item in the newsfeed is selected by the user in <em>FeedAdapter.kt</em>.</li><li>render binds the view state from the ViewModel to the view. The state values are used to create the UI as one would normally build and bind UI views.</li></ol><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f8610d55df999d955650baeee5f239cc/href">https://medium.com/media/f8610d55df999d955650baeee5f239cc/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9a54f85ab593f0a1ec2001e750e855d3/href">https://medium.com/media/9a54f85ab593f0a1ec2001e750e855d3/href</a></iframe><h4>Intent — View Interface</h4><p>The view interface is the connective network of Model-View-Intent, as it defines the contract of how the view and business logic interact, shares intents to the ViewModel, and renders the view states to the UI.</p><ol><li>initState, loadFromNetwork, and selectContent are functions that return a flow emitted from the view to the ViewModel.</li><li>render binds the view state emitted from the ViewModel to the Fragment. FeedView manually injects states into the view from the ViewModel vs. observing many pieces of data in different places liked done before in the Unidirectional Data Flow.</li></ol><p>Multiple render functions can be defined if more than one view state sealed class is needed.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9d8769f3498bd3ce960e6d0f823f8c2c/href">https://medium.com/media/9d8769f3498bd3ce960e6d0f823f8c2c/href</a></iframe><h3>Alternatives</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KSPGC5tKiaG3-V7D_JP4Dw.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@vladshap?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Vlad Shapochnikov</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>RxJava’s <a href="https://proandroiddev.com/rxjava-different-types-of-subjects-ef9183b5e87e"><em>Subject</em></a><em> </em>or<em> </em><a href="https://github.com/JakeWharton/RxRelay#rxrelay"><em>Relay</em></a> also work well with MVI. LiveData can be used too but will add undue complexity.</p><h4><strong>LiveData</strong></h4><p>Prior to utilizing Kotlin Flow, <a href="https://developer.android.com/topic/libraries/architecture/livedata"><em>LiveData</em></a> was the reactive framework in both the repository responses and ViewModel view state in version one, <a href="https://medium.com/hackernoon/android-unidirectional-flow-with-livedata-bf24119e747"><em>Android Unidirectional Data Flow with LiveData</em></a>.</p><p><strong>Not a good fit for Repositories — </strong>As outlined in <a href="https://proandroiddev.com/udf2-0-5052c3e1c62a"><em>Android Unidirectional Data Flow with LiveData — 2.0</em></a><em>, ‘</em><a href="https://proandroiddev.com/udf2-0-5052c3e1c62a#5a4f"><em>LiveData Can be Problematic in the Repository</em></a><em>’ </em>because its’ subscription is tied to a view lifecycle. LiveData is not relevant for non-UI related transformations where data needs to be processed in the Repository or ViewModel prior to populating the view state<em>.</em></p><p><strong>Adds view state complexity</strong> — After refactoring Flow to replace LiveData in the Repository, LiveData is still used in the view state. Additional complexity remains due to the need for a mutable and immutable view state class. Again, because LiveData is bound to a view’s lifecycle, a mutable view state cannot be initiated and observed in the ViewModel as with Flow or RxJava. As a workaround, the mutable view state adds values in the ViewModel, and the immutable view state is observed directly by the view instead of being emitted in the ViewModel through an interface contract like withFeedView.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a51579c3eaa310c0f0968c6517cf8ef5/href">https://medium.com/media/a51579c3eaa310c0f0968c6517cf8ef5/href</a></iframe><p>With Flow, the ViewModel observes state changes bound to the ViewModel as seen above where getViewModelScope sets the lifecycle to viewModelScope. With the release of StateFlow, <a href="https://medium.com/u/4762e889f8fc"><em>Roman Elizarov</em></a>, Kotlin Libraries Team Lead, also recommends to <a href="https://github.com/Kotlin/kotlinx.coroutines/issues/2034#issuecomment-631325914">consider <em>using MutableStateFlow as a MutableLiveData replacement</em></a><em>.</em></p><h4><strong>RxJava</strong></h4><p>For a comparison of Kotlin Flow and RxJava strengths see <a href="https://proandroiddev.com/udf-flowvsrx-a792b946d75c"><em>Android Unidirectional Data Flow — Kotlin Flow vs. RxJava</em></a><em>. </em><a href="https://medium.com/@AdamHurwitz/android-unidirectional-data-flow-coroutines-vs-rx-a792b946d75c#13cc"><strong><em>Spoiler</em></strong></a>, Kotlin Flow requires less setup than RxJava, manages the lifecycle by default, handles threading cleanly, avoids nesting, allows customization with extensions, and is relatively easy to use. Rx is still a great and powerful option as it has been battle-tested over the past six years and at the time of this post, holds the vast majority of market share in adoption for Android reactive frameworks.</p><h3>Summary</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*63gsGBjJMBYUQog46sXt6g.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@redevo?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Dave Robinson</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>The Unidirectional Data Flow 🌊 is a great initial step. With the addition of Model-View-Intent 🌲, and considering well-defined view states, it improves performance, read, and testability for the Coinverse beta app. Best yet, as MVI was originally popularized in JavaScript’s <a href="https://cycle.js.org/model-view-intent.html"><em>Cycle.js</em></a>, this pattern is effective across languages, frameworks, and platforms.</p><h4>View Contract</h4><p>The biggest takeaway for me is the view interface intents contract, FeedView. The FeedView contract is a concise map of all of the functionality between the view and the ViewModel. This concept was introduced to me by <a href="https://medium.com/u/9f0ead35e83b"><em>Yigit Boyar</em></a><em> </em>on the core Android team at the <a href="https://developer.android.com/dev-summit/"><em>2019 Android Dev Summit</em></a><em> </em>during an ‘office hours’ style session and took me time to realize how to refactor UDF into MVI + UDF properly.</p><h4>Unit Tests</h4><p>With the initial UDF Coinverse local JUnit 5 tests I spent too much time to debug the order of events fired and values observed. MVI will simplify this because instead of observing many individual values, each view state is observed as the direct result of an intent. <a href="https://medium.com/@AdamHurwitz"><em>Follow me on Medium</em></a><em> </em>for updates on using MVI for local Junit 5 tests.</p><p>This pattern is largely inspired by <a href="https://twitter.com/sockeqwe"><em>Hannes Dorfmann</em></a><em>’s </em>epic eight-part series, <a href="http://hannesdorfmann.com/android/mosby3-mvi-1"><em>Reactive Apps With Model-View-Intent</em></a> using RxJava. Hannes is also the creator of the Android MVI library <a href="https://github.com/sockeqwe/mosby#mosby"><em>Mosby</em></a>. The series is summarized in <a href="https://fragmentedpodcast.com/episodes/103/"><em>episode 103</em></a><em> </em>of the Fragmented podcast with <a href="https://twitter.com/kaushikgopal"><em>Kaushik Gopal</em></a> and <a href="https://twitter.com/donnfelker"><em>Donn Felker</em></a>. I highly recommend these resources. Also, thanks to <a href="https://www.linkedin.com/in/yoonhuh"><em>Michael Huh</em></a> for our discussions on your team’s version of MVI. It took me multiple times to fully digest the concepts so it is worth taking your time.</p><p>Explore <a href="https://play.google.com/store/apps/details?id=app.coinverse"><em>Coinverse</em></a> on the Google Play Store or the <a href="https://github.com/AdamSHurwitz/Coinverse#coinverse-open-app"><em>open-source</em></a> code. <strong>Please</strong> <strong>clap</strong> if you like the above. The longer the clap button is pressed the more claps. 👏🏻</p><p><strong>📝 Notes</strong></p><ul><li><a href="https://docs.google.com/document/d/13fmrGJbGHNEPo3FN7IDmwQjxjXK3qoACSRqjyawV32k/edit#heading=h.i0i19kug6lgk"><em>Model-View-Intent</em></a></li><li><a href="https://docs.google.com/document/d/13kerB3q1PBWFZSG0c-BMnX3oi6PWqdEVazFncj86Sos/edit#heading=h.g24l1nb3vbbj"><em>Kotlin Flow</em></a></li><li><a href="https://docs.google.com/document/d/1H4eCai4HROxWGxFi2LXcgaAgTdrW4hkldHHrFoRV8eI"><em>Dagger Dependency Injection</em></a></li></ul><h4>I’m Adam Hurwitz, writer of code and more. Follow me on <a href="https://medium.com/@adamhurwitz"><em>Medium</em></a>.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9Y68ZDgbgzGRkE0r.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ca5945316ec" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/android-model-view-intent-with-kotlin-flow-ca5945316ec">Android Model-View-Intent with Kotlin Flow</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Android Unidirectional Data Flow — Kotlin Flow vs. RxJava]]></title>
            <link>https://proandroiddev.com/udf-flowvsrx-a792b946d75c?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/a792b946d75c</guid>
            <category><![CDATA[kotlin-coroutines]]></category>
            <category><![CDATA[rxjava]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android-app-development]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Tue, 19 May 2020 05:18:27 GMT</pubDate>
            <atom:updated>2020-05-25T18:37:02.801Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OUk3m9jC8BRvdCp-zpdYOw.png" /><figcaption>Photo by <a href="https://unsplash.com/@iriser?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText"><em>Irina Iriser</em></a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h3>Android Unidirectional Data Flow — Kotlin Flow vs. RxJava</h3><h4>The network, database, and view states</h4><p><strong>One of the many great things about the Unidirectional Data Flow (UDF) to organize an app’s logic is that it works with any reactive programming pattern, whether it be Kotlin coroutines Flow (Flow), ReactiveX (Rx) based RxJava/Kotlin, or LiveData. Kotlin coroutines version </strong><a href="https://github.com/Kotlin/kotlinx.coroutines/releases?after=1.0.1"><strong><em>1.0 was released</em></strong></a><strong> at the end of 2018 and anecdotally has quickly been gaining adoption, alongside functionality. However, a large portion of apps have been built pre-late 2018 and rely on Rx. This makes both Flow and Rx important tools to be equipped with when working on a diverse set of codebases.</strong></p><p><strong><em>Spoiler — </em></strong><a href="https://medium.com/@AdamHurwitz/android-unidirectional-data-flow-coroutines-vs-rx-a792b946d75c#13cc"><strong><em>Kotlin Flow requires less setup than RxJava, manages the lifecycle by default, handles threading cleanly, avoids nesting, allows customization with extensions, and is relatively easy to use.</em></strong></a></p><p><em>CryptoTweets sample code</em></p><p><a href="https://github.com/AdamSHurwitz/CryptoTweets/">AdamSHurwitz/CryptoTweets</a></p><p><em>Previous</em></p><p><a href="https://proandroiddev.com/udf2-0-5052c3e1c62a">Android Unidirectional Data Flow with LiveData — 2.0</a></p><h3>Overview</h3><p><a href="https://medium.com/@AdamHurwitz/android-unidirectional-data-flow-kotlin-flow-vs-rx-a792b946d75c#22fd"><strong><em>Recap</em></strong></a></p><p><a href="https://medium.com/@AdamHurwitz/android-unidirectional-data-flow-kotlin-flow-vs-rx-a792b946d75c#84dd"><strong><em>Additional setups required</em></strong></a></p><p><strong>Data layer</strong></p><ul><li><a href="https://medium.com/@AdamHurwitz/android-unidirectional-data-flow-kotlin-flow-vs-rx-a792b946d75c#3a4c"><em>Retrofit network requests</em></a></li><li><a href="https://medium.com/@AdamHurwitz/android-unidirectional-data-flow-kotlin-flow-vs-rx-a792b946d75c#7aaf"><em>Room database with Paging library</em></a></li></ul><p><a href="https://medium.com/@AdamHurwitz/android-unidirectional-data-flow-kotlin-flow-vs-rx-a792b946d75c#1026"><strong><em>View states</em></strong></a></p><p><a href="https://medium.com/@AdamHurwitz/android-unidirectional-data-flow-kotlin-flow-vs-rx-a792b946d75c#13cc"><strong><em>Summary — Advantages of Kotlin Flow and RxJava</em></strong></a></p><h3>Recap</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*D2NZluDnjLE3N8d9.png" /><figcaption>UDF Structure</figcaption></figure><p><strong>🏗 UDF Structure</strong></p><ol><li>The view creates events initiated by the user or Android lifecycle.</li><li>The ViewModel requests data from the repository layer.</li><li>The repository returns data from the network/database.</li><li>The ViewModel creates/updates the view state, comprising the UI, and effects, which are one-time events like navigation or error-handling.</li><li>The view observes the view states/effect changes.</li></ol><p>💪🏻<strong> UDF Strengths</strong></p><ul><li>Linear flow of app logic with events initiating the data returned</li><li>Control UI &amp; async events</li><li>Pre-defined test structure</li><li>Quicker debugging</li></ul><h3>Additional setups required</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gZdDT3qVjmplps29rHPwhA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@jeremybishop?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Jeremy Bishop</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>In addition to implementing the core libraries for <a href="https://square.github.io/retrofit/#download"><em>Retrofit</em></a>, <a href="https://developer.android.com/training/data-storage/room/"><em>Room</em></a>, and the <a href="https://developer.android.com/jetpack/androidx/releases/paging"><em>Paging</em></a> tools below, additional setup is required for Flow and Rx.</p><h4>Core functionality</h4><p>🌊️️️ <strong>Flow — Add </strong><a href="https://github.com/Kotlin/kotlinx.coroutines/tree/master#kotlinxcoroutines"><strong><em>kotlinx.coroutines</em></strong></a></p><ul><li>When creating a new project in Android Studio select Kotlin as the language under the <a href="https://developer.android.com/studio/projects/create-project#configure"><em>Configure your project</em></a><em> </em>step.</li><li>For creating/updating the view state and effects, in addition to <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/"><em>Flow</em></a>, the kotlinx.coroutines library, starting with release <a href="https://github.com/Kotlin/kotlinx.coroutines/releases/tag/1.3.6"><em>1.3.6</em></a>, provides <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/index.html">StateFlow</a> and <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/index.html"><em>MutableStateFlow</em></a> similar to <a href="https://developer.android.com/topic/libraries/architecture/livedata"><em>LiveData</em></a>’s immutable and mutable variants.</li></ul><p>⛓️ <strong>Rx — Requires two additional libraries</strong></p><ul><li><a href="https://github.com/ReactiveX/RxKotlin#support-for-rxjava-3x-rxjava-2x-and-rxjava-1x"><em>RxKotlin</em></a> library for standard Rx functionality including RxJava</li><li><a href="https://github.com/ReactiveX/RxAndroid#binaries"><em>RxAndroid</em></a> library for Android-specific functionality like using the main thread with AndroidSchedulers.MainThread</li></ul><h4>Retrofit network requests</h4><p>🌊 ️️<strong>Flow — Works out of the box</strong></p><p>⛓ <strong>Rx — Add </strong><a href="https://github.com/akarnokd/RxJavaRetrofitAdapter"><strong><em>RxJava adapter</em></strong></a><strong> to Retrofit.</strong></p><ul><li>There are different RxJava adapter libraries based on the RxJava version number, as outlined in this <a href="https://stackoverflow.com/questions/42048520/rxandroid-and-retrofit-unable-to-create-call-adapter-for-io-reactivex-observabl/45075878#45075878"><em>StackOverflow post</em></a>.</li></ul><h4>Room SQL database and Paging library</h4><p>️🌊 <strong>Flow — Requires two additional libraries</strong></p><ul><li><a href="https://developer.android.com/training/data-storage/room/"><em>Room ktx extensions</em></a><em> </em>to enable Room to handle background threading</li><li><a href="https://developer.android.com/jetpack/androidx/releases/lifecycle"><em>LiveData lifecycle</em></a><em> </em>to convert the LiveData PagedList into a Flow using the asFlow extension method</li></ul><p>⛓️ <strong>Rx — Requires two additional libraries</strong></p><p><em>See: </em><a href="https://medium.com/androiddevelopers/room-rxjava-acb0cd4f3757"><em>Room 🔗 RxJava</em></a></p><ul><li><a href="https://developer.android.com/training/data-storage/room/accessing-data#query-rxjava"><em>Room database rxjava2</em></a><em> </em>for local SQL data storage</li><li><a href="https://developer.android.com/jetpack/androidx/releases/paging"><em>Paging rxjava2</em></a><em> </em>to connect the Room database to a RecyclerView</li></ul><h4>Additional setups summary</h4><p>In addition to the normal Retrofit, Room, and Paging setups, Flow requires fewer libraries compared to Rx.</p><p><em>build.gradle(:someModule)</em></p><p>🌊 <strong>Flow</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/36285fbbe7569fd3e8fcd060c6a60b83/href">https://medium.com/media/36285fbbe7569fd3e8fcd060c6a60b83/href</a></iframe><p>⛓️ <strong>Rx</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/94cd4d339bd37573b82d7e9d9e521ede/href">https://medium.com/media/94cd4d339bd37573b82d7e9d9e521ede/href</a></iframe><h3>Data layer — Retrofit network requests</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y3lOd7wFtWH3QOp28CKHqg.png" /><figcaption>Photo by <a href="https://unsplash.com/@scottwebb?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Scott Webb</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>Being able to handle both one-time requests and constant streams of information from the network are key capabilities of reactive programming.</p><h4><strong>Request return type</strong></h4><p>The type required for Flow, of a List, is simpler than using an Observable. Flow’s functions are slightly more complicated requiring the suspend modifier. However, this modifier allows Retrofit to handle background threading by default, as opposed to explicitly defining the threading when the Flow is launched. Also, returning a list is useful to handle information more easily as we’ll also see in the <strong>Check response status</strong> section below<strong>.</strong></p><p><em>FeedService.kt</em></p><p>🌊 <strong>Flow</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6dd93dea449bc90c63dbea84043bddbb/href">https://medium.com/media/6dd93dea449bc90c63dbea84043bddbb/href</a></iframe><p>⛓️ <strong>Rx</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cabbf9026f22bafab8aa27a66ecc5551/href">https://medium.com/media/cabbf9026f22bafab8aa27a66ecc5551/href</a></iframe><h4>Backpressure</h4><p>The sample in CryptoTweets<em> </em>showcases a simple one-time request. However, it is worth mentioning more advanced cases in which Flow and Rx have the ability to handle constant streams of data that may lead to backpressure. Backpressure is when there is an ongoing and incoming stream of data that needs to be processed. For instance, this can be from a web-socket type network request that maintains a constant connection. Managing backpressure requires processing the incoming data without causing issues such as slowing down the UI, crashing the app, or losing important information.</p><p>🌊 <strong>Flow</strong></p><p>Coroutines handle backpressure by default with the <a href="https://kotlinlang.org/docs/reference/coroutines/flow.html#flows">flow/</a><a href="https://kotlinlang.org/docs/reference/coroutines/flow.html#flows">collect</a> pattern by managing data streams synchronously. Flow by default performs sequentially. The code runs one emission from within the source flow, then one collection is run. This process continues until all of the data is processed.</p><blockquote>You would not see any code that explicitly handles back-pressure, because it automatically happens behind the scenes due to the support for suspension that is provided by Kotlin compiler. —<em> </em><a href="https://medium.com/u/4762e889f8fc">Roman Elizarov</a> (Kotlin Team Lead), <a href="https://medium.com/@elizarov/reactive-streams-and-kotlin-flows-bfd12772cda4#90c4">Reactive Streams and Kotlin Flows</a></blockquote><p>Coroutines offer further strategies to handle backpressure and improve the speed of which data is processed.</p><ul><li><a href="https://kotlinlang.org/docs/reference/coroutines/flow.html#buffering">buffer</a> — Runs the source data concurrently. If the collector is slow the data inside the source can still be pre-processed or manipulated so that when the collector is ready the data can be returned faster than if the source data was processed sequentially by default.</li><li><a href="https://kotlinlang.org/docs/reference/coroutines/flow.html#conflation">conflate</a> —This speeds up returning data by only returning the latest value, thus not returning all of the data from the source.</li><li><a href="https://kotlinlang.org/docs/reference/coroutines/flow.html#processing-the-latest-value">collectLatest</a> — Slow collectors are cancelled and restarted each time new data is emitted to increase the speed of data returned.</li></ul><p>⛓️ <strong>Rx</strong></p><p>By default, an Observable will not manage backpressure. If there is an existing source emitting data, and then is later observed, the data prior to the observation/subscription will not be processed. Instead, Rx handles backpressure using a Flowable which defines the strategy to handle backpressure.</p><ul><li>BackpressureStrategy.BUFFER — Allows the source to hold the data until the subscriber can consume it, similar to what coroutines do by default</li><li>BackpressureStrategy.DROP — Discards events that cannot be consumed in time by the subscriber.</li><li>BackpressureStrategy.LATEST — Overwrites previous data with new data if the subscriber is behind. This is most similar to coroutines’ conflate, because conflate <em>skips intermediate values</em>, but does not explicitly remove or drop them.</li></ul><p><em>See: </em><a href="https://www.baeldung.com/rxjava-2-flowable">RxJava 2 — Flowable</a>, <em>Baeldung</em></p><h4>Check response status</h4><p>After receiving information from a network request, doing a quick check on the status of the information is often required.</p><p>🌊 <strong>Flow</strong></p><p>Because the threading and lifecycle are defined at the initialization of the flow as shown in the <strong>Process data </strong>section below, the tweetsResponse may simply be checked for emptiness.</p><p><em>FeedRepository.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fba36655e2bf105911b3c24d4de2ec55/href">https://medium.com/media/fba36655e2bf105911b3c24d4de2ec55/href</a></iframe><p>⛓ ️<strong>Rx</strong></p><p>This requires subscribing to to the data and defining the threading in order to do a basic check, like whether the data is empty.</p><ol><li>subscribeOn — The thread that will launch the process</li><li>observeOn — The thread the data will be returned on.</li><li>subscribe — Returns the data. This pattern also creates a layer of nested code.</li></ol><p><em>FeedRepository.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/be3f02c4bff9d1d21a640dc106bbf280/href">https://medium.com/media/be3f02c4bff9d1d21a640dc106bbf280/href</a></iframe><h4>Process data</h4><p>🌊 <strong>Flow</strong></p><p>When the Flow is initialized the lifecycle is defined with launchIn. The <a href="https://developer.android.com/reference/kotlin/androidx/lifecycle/package-summary#viewmodelscope"><em>viewModelScope</em></a><em> </em>lifecycle inherently defines the Dispatchers.Main.immediate thread for data to be returned on. The lifecycle is automatically managed by the Android ViewModel using viewModelScope, and will be destroyed when the ViewModel is destroyed. Returning data on the main thread is appropriate because the the view state and UI are being populated/updated.</p><p>The logic within initFeed performs Retrofit and Room database tasks which if configured properly handle background threading by default. In other cases, flowOn needs to be defined explicitly before launchIn. i.e. flowOn(Dispatchers.IO) since the IO thread is appropriate for network and database tasks.</p><p><em>FeedViewModel.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a55632dbbed756f3c18d40a3cc4cd716/href">https://medium.com/media/a55632dbbed756f3c18d40a3cc4cd716/href</a></iframe><p><em>Side note — </em>The difference between <em>Dispatchers.Main.immediate</em> and <em>Dispatchers.Main</em>, as <a href="https://twitter.com/trionkidnapper"><em>Craig Russell</em></a> explains in <a href="https://medium.com/@trionkidnapper/launching-a-kotlin-coroutine-for-immediate-execution-on-the-main-thread-8555e701163b"><em>Launching a Kotlin Coroutine for immediate execution on the Main thread</em></a><em>, </em>is <em>Dispatchers.Main </em>requires a check first to ensure it is on the main thread, whereas <em>Dispatchers.immediate</em> assumes it is on the main thread and performs right away.</p><p>⛓️ <strong>Rx</strong></p><ol><li>The threading is defined with the same methods as in <strong>Check response status </strong>above<strong>.</strong></li><li>CompositeDisposable — Manages the Observable lifecycle. By adding the Observable to a CompositeDisposable, all of the created Observables can be cleared in the view activity/fragment onDestroy method by calling the public method in the ViewModel clearAndDisposeDisposables.</li></ol><p><em>FeedViewModel.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/768b57e427647196371499306b2cdd5f/href">https://medium.com/media/768b57e427647196371499306b2cdd5f/href</a></iframe><h3>Data layer — Room database with Paging library</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*neUppnOT7oCCYXrXq4R1Tg.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@davidclode?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">David Clode</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4><strong>Inserting and querying data from the DAO</strong></h4><p>🌊 <a href="https://developer.android.com/training/data-storage/room/accessing-data#kotlin-coroutines"><strong>Flow</strong></a></p><p>Both use a DataSource.Factory in the data access object in order to insert and query data from the Room SQL database. Flow utilizes the suspend modifier in order for Room to handle background threading by default. Again, this is opposed to explicitly defining the threading when the Flow is launched.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/58e217857d86f30b46e885f6d0e786d0/href">https://medium.com/media/58e217857d86f30b46e885f6d0e786d0/href</a></iframe><p>⛓ <a href="https://developer.android.com/training/data-storage/room/accessing-data#query-rxjava"><strong>Rx</strong></a></p><p><em>FeedDao.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c9ecbf1370a63eab61eeea2b0c4d7990/href">https://medium.com/media/c9ecbf1370a63eab61eeea2b0c4d7990/href</a></iframe><h4>Creating a PagedList</h4><p>🌊 <strong>Flow</strong></p><ol><li>getAllTweets — The <em>FeedDao </em>queries for data and the extension function toLiveData builds LiveData of type PagedList. toLiveData handles threading on an IO background thread.</li><li>asFlow — Converts this into a Flow of type PagedList bringing the data back onto a Main thread.</li><li>tweetsQuery — Data can be collected using collect and emitted using emit to the ViewModel which initiated the repository data request.</li></ol><p>Additional threading management could be added using <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html"><em>withContext</em></a> if further processing is required on other threads. Otherwise, the threading has already been defined when this method was initiated in the ViewModel above.</p><p><em>FeedRepository.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ac48e4735699d0cb17b769b6abc6aa9d/href">https://medium.com/media/ac48e4735699d0cb17b769b6abc6aa9d/href</a></iframe><p>⛓️ <strong>Rx</strong></p><ol><li>The process here is similar, but using toObservable extension function to convert the query directly to an observable instead of to LiveData first.</li><li>Threading is managed twice, first off in the ViewModel when building the view states and effects and initializing the repository request, and secondly here in the repository when processing the data.</li></ol><p><em>FeedRepository.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ddffe902f2b41c1d21b501e6c354a441/href">https://medium.com/media/ddffe902f2b41c1d21b501e6c354a441/href</a></iframe><h3>View states — States + effects</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qm0vHhl7rfXP4fGyaWcLvQ.png" /><figcaption>Photo by <a href="https://unsplash.com/@vinomamba24?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Vino Li</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>Regardless of the reactive implementation for the view state, (Flow, Rx, LiveData, etc.), there are both view states and effects to update in the ViewModel that create the user experience observed in the view, (activity/fragment).</p><ul><li><strong>View states </strong>— Final persisted data of a screen that<em> </em>is displayed continuously to the user</li><li><strong>View effects </strong>— One-time occurrences that don’t persist such as errors, dialogs, and navigation actions</li></ul><h4>View state</h4><p>🌊 <strong>Flow</strong></p><ol><li><a href="https://developer.android.com/reference/kotlin/androidx/lifecycle/package-summary#lifecyclescope"><em>lifecycleScope</em></a><em> — </em>Handles threading by default with lifecycleScope, returning the UI data onto the Dispatchers.Main.immediate thread the same as viewModelScope above.</li><li><a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/index.html"><em>MutableStateFlow</em></a> — Sets and updates the view state data in the ViewModel with a private mutable data class.</li><li><a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/"><em>Flow</em></a>— Updates the public non-mutable view state that is observed by the view, in this case a fragment. The view effects follow the same pattern.</li><li><a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/filter-not-null.html"><em>filterNotNull</em></a> — Allows the view state to be initialized as null when the ViewModel is created without emitting null values to the view.</li></ol><p><em>FeedViewState.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fbb0e25d8fbebb78daf904a94a7256aa/href">https://medium.com/media/fbb0e25d8fbebb78daf904a94a7256aa/href</a></iframe><p><em>FeedViewModel.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b0a4dfc63bab05f477fc228ba28f68ca/href">https://medium.com/media/b0a4dfc63bab05f477fc228ba28f68ca/href</a></iframe><p><em>FeedFragment.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6ccd69725fcb2e1fdcb72e0ec49e03d2/href">https://medium.com/media/6ccd69725fcb2e1fdcb72e0ec49e03d2/href</a></iframe><p>⛓️ <strong>Rx</strong></p><ol><li>BehaviorSubject — Rx <a href="http://reactivex.io/documentation/subject.html">Subjects</a> are both an Observer and an Observable, allowing their data to be mutated and observed. BehaviorSubject is a great fit for the view state and effects because the most recent value is emitted to the observers, and then any new values added during the lifecycle of the subscription are also emitted. The original design of the Rx view state in this sample used a ReplaySubject. All values are added to a ReplaySubject are emitted to its’ subscribers. With ReplaySubject, past values could potentially update the view state that are outdated. The Subject type used depends case-by-case.</li><li>All observers in the fragment must be cleared too in addition to clearing and disposing of the CompositeDisposable in the ViewModel above. With LiveData and Flow, the lifecycle is handled by default. This is a tradeoff if deciding between LiveData and Rx because LiveData handles lifecycle by default, whereas Rx allows specific emission strategies by default.</li></ol><p><em>See: </em><a href="https://proandroiddev.com/rxjava-different-types-of-subjects-ef9183b5e87e"><em>Exploring RxJava in Android — Different types of Subjects</em></a><em>, </em><a href="https://medium.com/u/b69ba9cd4ff2"><em>Anitaa Murthy</em></a></p><p><em>FeedViewState.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ea192d266ab4dc3901a406272d293d4d/href">https://medium.com/media/ea192d266ab4dc3901a406272d293d4d/href</a></iframe><p><em>FeedViewModel.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/edf03156fc68460522993bd4eff6566c/href">https://medium.com/media/edf03156fc68460522993bd4eff6566c/href</a></iframe><p><em>FeedFragment.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4be06db242c67e115dc4254fc434770f/href">https://medium.com/media/4be06db242c67e115dc4254fc434770f/href</a></iframe><h4>View effects</h4><p>The issue with any reactive programming pattern for one-time events is that they will be re-observed by the subscriber after the initial one-time event has been emitted. <a href="https://medium.com/u/e0a4c9469bb5">Jose Alcérreca</a> describes <a href="https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150"><em>the SingleLiveEvent case</em></a> in the context of LiveData. Both the issue and solution based on an event wrapper pattern can be applied to any reactive pattern.</p><p>For reach reactive pattern the type contained within the reactive object will be wrapped in an event. You can test this out by subscribing to a one-time event multiple times after emitting it once in the ViewModel and logging the value before and after implementing the Event.</p><p><em>Event.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/814bbecd45cd9ddeddb08b7b1aac86c6/href">https://medium.com/media/814bbecd45cd9ddeddb08b7b1aac86c6/href</a></iframe><p>🌊 <strong>Flow</strong></p><p>The view effect is similar to the view state with the only difference being the Event wrapper class.</p><p><em>FeedViewEffect.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8435ecb02e78f4206a343886e5661c87/href">https://medium.com/media/8435ecb02e78f4206a343886e5661c87/href</a></iframe><p><em>FeedViewModel.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c90dff90b458ee8d6f7f0cde06619f7d/href">https://medium.com/media/c90dff90b458ee8d6f7f0cde06619f7d/href</a></iframe><p>It is easy to create customized utility functions with Kotlin. The one-time event is a perfect use case. Instead of manually checking in the view whether the event has already been emitted with getContentIfNotHandled from the Event class, this is performed automatically in a custom utility function onEachEvent, customizing the coroutines’ <a href="https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt#L72"><em>Transform.kt</em> </a><a href="https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt#L72">onEach</a> extension function.</p><p><em>OnEachEvent.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a609f6505634daf52126b429ca7bf4a3/href">https://medium.com/media/a609f6505634daf52126b429ca7bf4a3/href</a></iframe><p><em>FeedFragment.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/01385f5f8e129fe1c32e12fe1eaf4bba/href">https://medium.com/media/01385f5f8e129fe1c32e12fe1eaf4bba/href</a></iframe><p>⛓️ <strong>Rx</strong></p><p><em>FeedViewEffect.kt</em></p><p>The same Event class is used as the BehaviorSubject type.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/12a159d9d2dd4be9f08109cde4d6d3fc/href">https://medium.com/media/12a159d9d2dd4be9f08109cde4d6d3fc/href</a></iframe><p><em>FeedViewModel.kt</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6d1ad3d5bdd317d4c183660a9b2ee2bc/href">https://medium.com/media/6d1ad3d5bdd317d4c183660a9b2ee2bc/href</a></iframe><p><em>FeedFragment.kt</em></p><p>getContentIfNotHandled manually checks to see if the event has already been observed.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9b5c30616ba8f1f4b2536c7ca91ab84b/href">https://medium.com/media/9b5c30616ba8f1f4b2536c7ca91ab84b/href</a></iframe><p>subscribe is written in Java and cannot be created as easily as the onEachEvent extension function for Kotlin Flow. I’m open to pull requests if you’d like to customize subscribe. Good luck.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FkVaj8JXJcDsqs%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fgiphy.com%2Fgifs%2Fmorgan-freeman-good-luck-kVaj8JXJcDsqs&amp;image=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FkVaj8JXJcDsqs%2Fgiphy.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="295" frameborder="0" scrolling="no"><a href="https://medium.com/media/92ba3f9d71ca06aea60bb2440a993fc2/href">https://medium.com/media/92ba3f9d71ca06aea60bb2440a993fc2/href</a></iframe><h3>⚖️ Summary — Advantages of Kotlin Flow and RxJava</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R-JuAQI5QdtsYZmha509Yg.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@danist07?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">贝莉儿 DANIST</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>In cases working with legacy code or teams not yet onboard with Kotlin Flow, it is important to be able to write well structured code with Rx so that all of the developers working on common code adopt a similar strategy. Given the opportunity to create a new app/feature, Kotlin Flow has a short learning curve, built-in integrations with Android components and widely adopted open-source libraries, and expanding it’s use cases rapidly.</p><p>🌊 <strong>Flow</strong></p><ul><li>🏎️ <strong>Quick setup</strong> — Requires two fewer libraries compared to Rx.</li><li>♻️ <strong>Lifecycle management</strong> — Inherently controls the lifecycle of the operation upon launch.</li><li>🧵 <strong>Threading management</strong> — Can be handled in one place when the flow is launched rather than each time data is observed. Additional customization can be added if needed. This results in cleaner and simpler code.</li><li>👻 <strong>Avoid nesting nightmares</strong> — The callback style pattern is harder to work with and creates the need to initialize instance variables within a function to retrieve information from within nested logic, like when creating the PagedList above.</li><li>🎨 <strong>Customization —</strong> The core classes, methods, and inherited classes and interfaces are readable and easy to understand the existing features in order to edit or add functionality with extension functions.</li><li>🚦 <strong>Ease-of-use — </strong>It is easy to learn new topics via reading the good ol’ <a href="https://kotlinlang.org/docs/reference/android-overview.html">documentation</a>. The docs provide interactive examples for almost every topic to test, iterate, break, and understand. <em>Does anyone truly understand the marbles diagrams in Rx? </em>🤔</li></ul><p>⛓️ <strong>Rx</strong></p><ul><li>🧑🏽‍🏭<strong>Mass adoption — </strong>Most apps built pre-2018 use Rx.</li><li>⚔️️<strong>Battle tested APIs </strong>— Kotlin Flow contains many experimental APIs, indicated by@ExperimentalCoroutinesApi, which is temporary as the libraries mature.</li><li>⚙️<strong>Larger selection of operators — </strong>In CryptoTweets the data stream is simple, but in more complex cases, due to the maturity of Rx, there might be more advanced operators/transformations not yet available with Kotlin coroutines and Flow. For both CryptoTweets and the beta app I’ve published to the Play Store, <a href="https://play.google.com/store/apps/details?id=app.coinverse">Coinverse</a>, I have not run into such cases where coroutines have not been able to do the job.</li></ul><p><strong><em>I’d love to hear in the comments below if there are operators you’ve come across for RxJava that are not yet available for Kotlin coroutines/Kotlin Flow.</em></strong></p><p>Thus far, <a href="https://medium.com/u/b040a35aa2f0">Andrey Bolduzev</a> has pointed out Rx’s distinct operator for filtering only unique values for an entire subscription’s stream as outlined <a href="https://proandroiddev.com/exploring-rxjava-in-android-operators-for-filtering-observables-b0a299ea23a3">here</a>.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2Fl41YnSDHZiUpTK5gI%2Ftwitter%2Fiframe&amp;display_name=Giphy&amp;url=https%3A%2F%2Fgiphy.com%2Fgifs%2Fgame-of-thrones-good-luck-i-wish-you-fortune-in-the-wars-to-come-l41YnSDHZiUpTK5gI&amp;image=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2Fl41YnSDHZiUpTK5gI%2Fgiphy.gif&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=giphy" width="435" height="249" frameborder="0" scrolling="no"><a href="https://medium.com/media/45ba46004c3bc3d52c21e5db546098c2/href">https://medium.com/media/45ba46004c3bc3d52c21e5db546098c2/href</a></iframe><p><em>Thank you to </em><a href="https://medium.com/u/4762e889f8fc"><em>Roman Elizarov</em></a><em> for the </em><a href="https://github.com/Kotlin/kotlinx.coroutines/issues/2034"><em>feedback</em></a><em> regarding Kotlin StateFlow and </em><a href="https://www.linkedin.com/in/andres-mauricio-rubiano-del-chiaro-59765a96/">A<em>ndres Rubiano Del Chiaro</em></a><em> with RxJava! </em>🙏🏻 Please leave claps if you liked the above. The longer the clap button is pressed the more claps. 👏🏻</p><h4>I’m Adam Hurwitz, writer of code and more. Follow me on <a href="https://medium.com/@adamhurwitz">Medium</a>.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*B-8IT6acTqG10oks.png" /></figure><h3>📝 Notes</h3><ul><li><a href="https://docs.google.com/document/d/1xANxIeduaDMR2ZTZ9efkHf7YXyLs4osGnXp9odNgQNE/edit?rm=minimal"><em>Kotlin Coroutines</em></a></li><li><a href="https://docs.google.com/document/d/13kerB3q1PBWFZSG0c-BMnX3oi6PWqdEVazFncj86Sos?rm=minimal#heading=h.g24l1nb3vbbj"><em>Kotlin Flow</em></a></li><li><a href="https://docs.google.com/document/d/1jqJwaTlCRdrTIMp3dLNw3FeHSf6NqomK15L5GkcKQZw?rm=minimal#heading=h.8hlgrbne9cni"><em>RxJava</em></a></li><li><a href="https://docs.google.com/document/d/1dAom3ExqT6yaVfRjfGLxbImbzzad8ltgEjKkZUAqNg4?rm=minimal#heading=h.x326lbp27xg"><em>RxKotlin</em></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a792b946d75c" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/udf-flowvsrx-a792b946d75c">Android Unidirectional Data Flow — Kotlin Flow vs. RxJava</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Optimizing ViewModel with Lifecycle 2.2.0]]></title>
            <link>https://proandroiddev.com/optimizing-viewmodel-with-lifecycle-2-2-0-a2895b5c01fd?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/a2895b5c01fd</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Sun, 23 Feb 2020 23:47:32 GMT</pubDate>
            <atom:updated>2020-03-27T21:16:46.073Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*voHEHCw6ZWrWGMmZ_xtpBQ.png" /><figcaption>Photo by <a href="https://unsplash.com/@lennykuhne?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Lenny Kuhne</a> on <a href="https://unsplash.com/@lennykuhne?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h3>Optimizing Android ViewModel with Lifecycle 2.2.0</h3><h4>Initialization, passing arguments, and saved state</h4><p><strong>The </strong><a href="https://developer.android.com/jetpack/androidx/releases/lifecycle#version_220_3"><strong>Lifecycle 2.2.0</strong></a><strong> update including simplified initialization with</strong> by viewModels(), by activityViewModels(), and by navGraphViewModels(...) <strong>syntax for the ViewModel (VM) component is great for quickly creating VMs.</strong> <strong>What about re-using the VM instance throughout fragments, passing arguments/parameters into the VM while also enabling saved state? With a bit of customization, the above can be achieved as I recently shipped in the latest version of the </strong><a href="https://play.google.com/store/apps/details?id=app.coinverse"><strong>Coinverse app</strong></a><strong>.</strong></p><h3>Sample Code</h3><p><a href="https://github.com/AdamSHurwitz/Coinverse">AdamSHurwitz/Coinverse</a></p><p><a href="https://medium.com/@AdamHurwitz/a2895b5c01fd#30cb">Setup</a></p><p><a href="https://medium.com/@AdamHurwitz/a2895b5c01fd#e850">Initializing the ViewModel for Reusability</a></p><p><a href="https://medium.com/@AdamHurwitz/a2895b5c01fd#59f4">How to pass Arguments/Parameters to ViewModels</a></p><p><a href="https://medium.com/@AdamHurwitz/a2895b5c01fd#aade">Enabling SavedState with Arguments/Parameters</a></p><p><a href="https://medium.com/@AdamHurwitz/a2895b5c01fd#e08a">Resources</a></p><h3>Setup</h3><p>To take advantage of the latest VM component, declare the most recent <a href="https://developer.android.com/jetpack/androidx/releases/lifecycle#declaring_dependencies">Lifecycle dependencies</a> in the <em>build.gradle</em> <em>(Module: app)</em> file for lifecycle-viewmodel-ktx and lifecycle-livedata-ktx. The jvmTarget also needs to be defined in order to implement the by viewModels syntax we’ll use below.</p><p>If the navigation component version of the VM is being implemented, the <a href="https://developer.android.com/jetpack/androidx/releases/navigation">navigation components’ libraries</a>, navigation-ui-ktx and navigation-fragment-ktx are required.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b67dc97aa9525acb190bd4bd9a484e77/href">https://medium.com/media/b67dc97aa9525acb190bd4bd9a484e77/href</a></iframe><h3>Initializing the ViewModel for Reusability</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Po_iQ0JrM7EiVshbkp3Uug.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@stephanie_moody?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Stephanie Moody</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>As the <a href="https://developer.android.com/topic/libraries/architecture/viewmodel?utm_campaign=android_series_viewmodeldoc_110817">ViewModel documentation</a> outlines, a major advantage of utilizing VMs in an activity/fragment is the ability to save view state data that comprises the user interface. Otherwise, large portions of data would need to be reloaded every time there is a configuration change, or when the app temporarily goes into the background.</p><p>The documentation shows initiating the VM in onCreate as a method level var, and then later on as a class level val. Creating the VM in onCreate requires reassigning the variable on the class level in order to use the VM throughout the activity/fragment. For this reason, it’s preferred to <a href="https://developer.android.com/topic/libraries/architecture/viewmodel?utm_campaign=android_series_viewmodeldoc_110817#sharing">create the VM as a class level </a><a href="https://developer.android.com/topic/libraries/architecture/viewmodel?utm_campaign=android_series_viewmodeldoc_110817#sharing">val</a>.</p><p>Rather than creating a lateinit var instance variable, declare a VM immutable val. The VMs values can only be accessed after the activity is attached to the application.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f6ee2327ba2607c1398449b5d2a3c2b9/href">https://medium.com/media/f6ee2327ba2607c1398449b5d2a3c2b9/href</a></iframe><h4>🔎 Differences between by viewModels, activityViewModels, and navGraphViewModels</h4><ul><li>Use by viewModels when the activity/fragment creating the VM will be the only activity/fragment accessing the VM’s data.</li></ul><p>Behind the scenes <a href="https://developer.android.com/reference/kotlin/androidx/activity/package-summary#viewmodels">viewModels</a> is an extension function applied to either an activity/fragment that returns a VM with a lifecycle tied to that activity/fragment. A VM factory may optionally be defined which can be used to pass parameters/arguments as we’ll see below.</p><ul><li>Use by activityViewModels in fragments, when the fragment is sharing data/communicating with another activity/fragment.</li></ul><p>This is useful for sharing information between views. <a href="https://developer.android.com/reference/kotlin/androidx/fragment/app/package-summary#activityviewmodels">activityViewModels</a> is an extension function applied to a fragment that returns a fragment’s parent’s activity’s VM. A VM factory may be defined here too.</p><ul><li>Use by navGraphViewModels(R.id.some_nav_graph) in a fragment where the VM benefits by being scoped to a specific navigation graph or nested graph.</li></ul><p><a href="https://developer.android.com/guide/navigation/navigation-programmatic#share_ui-related_data_between_destinations_with_viewmodel">This</a> is useful for managing the business logic of features organized in a nested graph to ensure sure data is not leaked to other parts of the app, and that the lifecycle is handled properly. A VM factory may be defined here as well.</p><p><em>See: </em><a href="https://medium.com/androiddevelopers/viewmodels-with-saved-state-jetpack-navigation-data-binding-and-coroutines-df476b78144e#d2bd">ViewModel and Jetpack Navigation : NavGraph with a ViewModel</a></p><h4>📦 <strong>Storing view state data</strong></h4><p>The VM is a great location to store cached view state data using an observable data structure such as <a href="https://developer.android.com/topic/libraries/architecture/livedata">LiveData</a>, <a href="https://developer.android.com/topic/libraries/architecture/coroutines">Coroutines</a>, or <a href="https://github.com/ReactiveX/RxKotlin">RxKotlin</a>.</p><ul><li>Use immutable public values to observe data.</li><li>Use mutable private variables to edit values in the VM.</li><li>Use a pattern that allows the readability of the values in the VM, such as LiveData’s <a href="https://developer.android.com/topic/libraries/architecture/livedata#transform_livedata">transform</a> or Coroutine’s <a href="https://codelabs.developers.google.com/codelabs/advanced-kotlin-coroutines/#13">collect</a>.</li></ul><h4>♻️ <strong>VM lifecycle overview</strong></h4><ul><li>Created the first time an activity calls onCreate</li><li>When an activity is recreated, it receives the same VM instance.</li><li>When the creating activity/fragment is finished/detached, VM’s onCleared is called</li></ul><h3>How to pass Arguments/Parameters to ViewModels</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4U9-bYTXxzuDqQXc93dnBQ.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@wilhelmgunkel?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Wilhelm Gunkel</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>The advantage of passing data into the VM upon creation is that an important process may begin upon VM initialization, such as retrieving data to populate a view, rather than the process being tied to the fragment/activity lifecycle. This is important because as <a href="https://twitter.com/CeruleanOtter">Lyla Fuijwara</a>, a Google developer advocate explains, the VM instance is created once inside the activity/fragment. Then, the VM is stopped only when a user closes the view holding the VM, closes the app entirely, or if the Android system needs to clear the memory while the app is in the background.</p><p>If an initialization process is tied to an activity/fragment lifecycle event, it may unintentionally occur if the activity/fragment is recreated. When the process runs in the VM’s init{..} function, it will be tied to the VM lifecycle.</p><p>See: <a href="https://medium.com/androiddevelopers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090"><em>ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders</em></a></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/782f5e35141f3388d04f77ff8ecaa6d0/href">https://medium.com/media/782f5e35141f3388d04f77ff8ecaa6d0/href</a></iframe><h4>🤷🏻‍♂ Can’t we just use AndroidViewModel (AVM)?</h4><p>AVM is an Android architecture component after all and allows one to pass parameters while providing application context. This seems tempting at first, however, it is problematic for unit tests. Unit tests should not deal with any of the Android lifecycle, such as context.</p><h4>☝🏻 Data not to pass to the ViewModel</h4><ul><li>Context, views, activities, fragments, adapters, Lifecycle, observe lifecycle-aware observables or hold resources, drawables etc. This information should be handled by the view and tethered to the view lifecycle.</li><li>Don’t load resources, instead pass resource ids, and load in view. See: <a href="https://medium.com/androiddevelopers/locale-changes-and-the-androidviewmodel-antipattern-84eb677660d9"><em>Locale changes and the AndroidViewModel antipattern</em></a></li></ul><h3>Enabling SavedState with Arguments/Parameters</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EB5rdW3aVNv_9NwpbJZ14Q.jpeg" /><figcaption>Photo by <a href="https://www.flickr.com/photos/pestoverde/">Maurizio Pesce</a> on <a href="https://www.flickr.com/">flickr</a></figcaption></figure><p>The <a href="https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate">saved state module</a> is a lifecycle component enabling saving data in the VM that will persist when the VM is stopped by the Android system. This may replace the functionality of saving data inside onSaveInstanceState (OSIS) using the activity/fragment bundle. Lyla also highlights the differences between onSaveInstanceState and SavedState (SS) in detail. The main difference with SS is that the data is stored within the VM instead of in the activity/fragment with OSIS.</p><p>OSIS and SS are meant to serve the same use case, saving small amounts of information required to restore the view state of the screen. The majority of data in the VM is meant for saving larger amounts of data that make up the view state. Therefore, SS may replace OSIS in order to organize both the smaller and larger view state data in one place, the VM.</p><p>See: <a href="https://medium.com/androiddevelopers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090#81a0"><em>How do I use ViewModels to save and restore UI state efficiently?</em></a></p><p>With the Lifecycle 2.2.0 release, the saved state module comes standard with by viewModels or by activityViewModels syntax, without needing to create a ViewModelProvider.NewInstanceFactory. However, if setting default values for the saved state module or passing arguments/parameters to a VM, an <a href="https://developer.android.com/reference/androidx/lifecycle/AbstractSavedStateViewModelFactory">AbstractSavedStateViewModelFactory</a> factory is required, as the <a href="https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate#setup">documentation</a> notes. The factory class must handle the saved state owner, default values, and custom parameters.</p><h4>🚦<strong>DefaultArgs</strong></h4><p>The Bundle defaultArgs is recommended to pass into the ViewModelFactory, as the values will be used as defaults in the <a href="https://developer.android.com/reference/androidx/lifecycle/SavedStateHandle"><em>SavedStateHandle</em></a><em> </em>if there is not a value for the given key being requested by the saved state.</p><p>See: <a href="https://developer.android.com/reference/androidx/lifecycle/AbstractSavedStateViewModelFactory#public-constructors_1"><em>Public constructors</em></a><em> </em>of the AbstractSavedStateViewModelFactory</p><h4>💾 <strong>Saved State</strong></h4><p>An Int is saved with the method saveSomeInt, in the SavedStateHandle to represent important information that needs to survive the VM being cleared. Then, someInt may be retrieved when needed. Because a default argument is defined for the key SOME_INT_KEY, the saved state will always have a value to work with for that key.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/858f456cffa231cf168cc665480f6912/href">https://medium.com/media/858f456cffa231cf168cc665480f6912/href</a></iframe><h3>Resources</h3><ul><li>🏞 <strong>Check out the Coinverse app in the wild!</strong></li></ul><p><a href="https://play.google.com/store/apps/details?id=app.coinverse">Coinverse - cryptocurrency news, bitcoin, ethereum - Apps on Google Play</a></p><ul><li>📝 <a href="https://docs.google.com/document/d/1KkxQXDtA3zJ1ledARXr3IRdciYdrIJj40zNwRSJP6zU/edit?rm=minimal">ViewModel notes</a></li></ul><h4><strong>I’m Adam Hurwitz, writer of code and more. Follow me on </strong><a href="https://medium.com/@adamhurwitz"><strong>Medium</strong></a><strong> and </strong><a href="https://twitter.com/AdamSHurwitz"><strong>Twitter</strong></a><strong>.</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Hvcjp6Nc9S8x8QEp.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a2895b5c01fd" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/optimizing-viewmodel-with-lifecycle-2-2-0-a2895b5c01fd">Optimizing ViewModel with Lifecycle 2.2.0</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Android Unidirectional Data Flow — Local Unit Testing]]></title>
            <link>https://proandroiddev.com/android-unidirectional-data-flow-local-unit-testing-487a6e6f5c9?source=rss-6049ba9d2bbd------2</link>
            <guid isPermaLink="false">https://medium.com/p/487a6e6f5c9</guid>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Adam Hurwitz]]></dc:creator>
            <pubDate>Wed, 01 Jan 2020 19:29:56 GMT</pubDate>
            <atom:updated>2020-01-02T20:28:43.460Z</atom:updated>
            <content:encoded><![CDATA[<h3>Android Unidirectional Data Flow — Local Unit Testing</h3><h4>MockK, JUnit 5, and AssertJ</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qyFFktFaMeTMJY7wa3QkfQ.png" /><figcaption>Photo by <a href="https://unsplash.com/@jeremybishop?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Jeremy Bishop</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p><strong>When I started building the Unidirectional Data Flow (UDF) with LiveData pattern, I did not have testing experience. Working on a small/fast moving startup team, Close5, owned by eBay, and building Coinverse from scratch, I had not been required, nor did I allocate the time to develop tests. One of the touted advantages of UDF is optimizing the architecture for testing. This made for a great opportunity to develop testing fundamentals explored in the Coinverse code below.</strong></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fplayer.vimeo.com%2Fvideo%2F380844755%3Fapp_id%3D122963&amp;dntp=1&amp;url=https%3A%2F%2Fplayer.vimeo.com%2Fvideo%2F380844755&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=vimeo" width="1920" height="1080" frameborder="0" scrolling="no"><a href="https://medium.com/media/9514315e77f5bf8ce055c5c34b0d7753/href">https://medium.com/media/9514315e77f5bf8ce055c5c34b0d7753/href</a></iframe><p>Previous:</p><p><a href="https://medium.com/@AdamHurwitz/udf2-0-5052c3e1c62a">Android Unidirectional Data Flow with LiveData - 2.0</a></p><p>Sample code:</p><p><a href="https://github.com/AdamSHurwitz/Coinverse">AdamSHurwitz/Coinverse</a></p><h3>Strategy — UDF + JUnit 5</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LXCNMuUJXBwKCXC8QU5c8w.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@nathananderson?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Nathan Anderson</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>Local unit tests are a great beach head to start with if you’re new to writing tests. With the UDF flow the test cares are already organized. Local unit tests provide a good base to build from, as you can then expand to integration and instrumented tests.</p><ul><li><strong>Unit</strong> — Test independent code modules containing business logic, also used for integration and instrumented tests. Mocking components the module is dependent on is required.</li><li><strong>Integration</strong> — Test components app is dependent upon, <em>i.e. Repository.</em></li><li><strong>Instrumented</strong> — Test end-to-end behavior of a real device or emulator, including UI (Espresso) tests.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*IjjezWt_wSjJCYPwf3Oppg.png" /><figcaption>UDF + JUnit 5 strategy</figcaption></figure><p>To create local unit tests the UI (view), business logic(ViewModel), and data requests (Repository) will be modularized, so that they are independent of each other. Then, the ViewModel&#39;s business logic can be tested using JUnit 5.</p><h3>Tools 🧰</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eHR8w5IRnC78LY_FhO8COQ.png" /></figure><h4>JUnit 5</h4><ul><li>♻️ Reusable tests with dependency injection and extensions (rules in JUnit 4)</li><li>🏷️ Updated lifecycle naming</li><li>🏎️ Faster core library updates</li></ul><h4>MockK</h4><ul><li>🥇 Kotlin first</li><li>🌄 Native coroutines support</li><li>🛰 ️Great for mocking Repository object, top-level functions/values, and static Java methods</li></ul><h4>AssertJ</h4><p>There are many great assertion libraries so there is more flexibility of options here. Because UDF organizes the final persisted data (view) into a state object and one time navigation events into an effects objects, AssertJ tests the view state/effect values.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*qrpTb8Cugo0CGmIbut-dZQ.png" /><figcaption>AssertJ tests the view state and effects data</figcaption></figure><p>Now that the tools are laid out we’ll use them by creating view events with <a href="https://junit.org/junit5/docs/current/user-guide/"><strong>JUnit 5</strong></a>, mocking the data request responses from the Repository with <a href="https://mockk.io/"><strong>MockK</strong></a>, and testing the correct data is returned with <a href="https://joel-costigliola.github.io/assertj/"><strong>AssertJ</strong></a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*Rk7winDtAjRkYw8uKkeWhA.png" /><figcaption>UDF + JUnit 5 strategy summary</figcaption></figure><h3>Modularize Components</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-dwbzx2qCIBI5CUuUbvgKw.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@spacex?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">SpaceX</a> on <a href="https://unsplash.com/s/photos/satellite?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>We’ll create components independent of each other so that the code not being tested is able to be mocked. For example, we want to avoid situations within the ViewModel where a Repository instance is created, or inside of a Repository where a database instance is created. If these dependancies exist it complicates running the code in the local unit test environment because the environment does not have the same level of access to the Android system.</p><h4>Dependency Injection</h4><p>To keep things simple I implemented Singletons to initiate commonly used components on the application level. As Coinverse scales a dependency injection/service locator library may be implemented.</p><p>Modularization has been a hotly contested topic in the Twitter + ReddIt(verses). At the 2019 Android Developer Summit Google announced increased focus on Dagger2 dependency injection, including better Kotlin support. There are debates regarding dependency injection vs. service locators.</p><p><em>See </em><a href="https://docs.google.com/document/d/1H4eCai4HROxWGxFi2LXcgaAgTdrW4hkldHHrFoRV8eI/edit?rm=minimal#"><em>Gdoc notes — Dependency Injection / Service Locators</em></a><em>.</em></p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fo-ins1nvbDg%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Do-ins1nvbDg&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fo-ins1nvbDg%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/1f57d258be278d5554e08dc3e6ebc64e/href">https://medium.com/media/1f57d258be278d5554e08dc3e6ebc64e/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=reddit&amp;url=https%3A//www.reddit.com/r/androiddev/comments/8ch4cg/dagger2_vs_koin_for_dependency_injection/&amp;image=" width="600" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/a3d7968234e484b4a736eaa96079ddab/href">https://medium.com/media/a3d7968234e484b4a736eaa96079ddab/href</a></iframe><h3>Jake Wharton on Twitter</h3><p>@HariOfSpades @lime_cl @KaenDagger I think I need to prequel that with a &quot;The Android documentation is teaching you terrible architecture practices&quot; which is the underlying cause for most of the pain around testing, DI, and a bunch of other fundamental things.</p><h4>Singletons</h4><p>To create Singleton module instances on the application level we can override the <em>Application</em> class and create an instance of the component(s) to be used throughout the lifecycle of the app.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e441339b2255c9f9605e6885365d4f9a/href">https://medium.com/media/e441339b2255c9f9605e6885365d4f9a/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e11eeb96322c973f4bed154367f8c8de/href">https://medium.com/media/e11eeb96322c973f4bed154367f8c8de/href</a></iframe><p>The Repository is created as a Singleton object instance. We can use the global database instance created in the <em>Application</em> class to access the database independently inside of the Repository.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d3f5d799c385a405d05939852f2789cb/href">https://medium.com/media/d3f5d799c385a405d05939852f2789cb/href</a></iframe><p>Because the Repository is also a Singleton we can cleanly call it from within the ViewModel. This allows us to create the ViewModel moving forward in tests without requiring on dependencies from other components like the Repository.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c805bf05796ae9b920998bba67dca5a1/href">https://medium.com/media/c805bf05796ae9b920998bba67dca5a1/href</a></iframe><h3>Test Implementation Steps</h3><p>Now that the ViewModel does not have dependent components created inside of it, we can setup the test configuration.</p><p><strong>1a. Configuration — Extensions</strong>, <em>JUnit 5</em></p><ul><li><strong>Coroutines + </strong><strong>LiveData</strong></li><li><strong>Component injection</strong></li></ul><p><strong>1b. Configuration — Parameterized tests</strong>, <em>JUnit 5</em></p><p><strong>2.</strong> <strong>Mock components</strong>,<strong> </strong><em>MockK</em></p><p><strong>3.</strong> <strong>Create view events</strong>,<strong> </strong><em>JUnit 5</em></p><p><strong>4.</strong> <strong>Make assertions</strong>,<strong> </strong><em>AssertJ</em></p><p><strong>5. Verify tests</strong>,<strong> </strong><em>MockK</em></p><p>Creating the configuration and mock components, steps <strong>1–2</strong>, is 80% of the work. Initiating the view events, testing the view state/effects, and verifying the methods is easier, steps <strong>3–5</strong>.</p><h3>1a. Configuration — Extensions</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H56aExSH1kppdv17qzkhGQ.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@unstable_affliction?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Ivan Bandura</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p><a href="https://junit.org/junit5/docs/current/user-guide/#extensions">Extensions</a> are a new feature of JUnit 5 allowing for modularization and re-using components across tests to improve speed and efficiency. In JUnit 4 rules allowed similar functionality. The JUnit team indicates extensions improve upon rules by creating the ability to provide more frequent improvements because extensions can be updated easier.</p><p>I took advantage of extensions to re-use the logic for the following.</p><ul><li>Test lifecycle methods</li><li>Injecting common components withParameterReslolver</li></ul><p>The test class implements an extension with the ExtendWith annotation, and injects the components of type CoroutineDispatcher and ViewModel.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ca1022e9ba460aa2d6d54100add36730/href">https://medium.com/media/ca1022e9ba460aa2d6d54100add36730/href</a></iframe><p>The extension can access lifecycle methods and ParameterResolver for injecting components.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6e024c7e298dfbad425b28cdfa3bf091/href">https://medium.com/media/6e024c7e298dfbad425b28cdfa3bf091/href</a></iframe><h4>Coroutines + LiveData</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r9XomAz_WM-xUuYA1twg2Q.png" /></figure><p>In order for Coroutines and LiveData to work outside of the Android framework in the JUnit 5 local unit tests, we need to set a test CoroutineDispatcher and LiveData executor.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3b271d12206fbd37bf4861723da109a2/href">https://medium.com/media/3b271d12206fbd37bf4861723da109a2/href</a></iframe><ol><li>In thebeforeEach test lifecycle method the CoroutineDispatcher and LiveData executor are created before each test.</li><li>In afterEach the CoroutineDispatcher and LiveData are reset and cleared after each test.</li><li>We’ll look at the usage of getStore in the next <strong>Component Injection </strong>section to reuse components.</li></ol><p><strong>CoroutineDispatcher</strong> — Handles launching new Coroutines within tests</p><ul><li>Replaces main dispatcher with test dispatcher</li><li>Creates mock thread pool for testing</li><li><em>See </em><a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/#providing-an-explicit-testcoroutinedispatcher"><em>Providing an explicit TestCoroutineDispatcher</em></a><em> (</em><a href="https://kotlin.github.io/kotlinx.coroutines/"><em>Kotlin documentation</em></a><em>)</em></li></ul><p><strong>ArchTaskExecutor</strong> — Update live data immediately (don’t post value)</p><ul><li>Don’t update live data on the main thread</li><li><em>See </em><a href="https://jeroenmols.com/blog/2019/01/17/livedatajunit5/"><em>JVM unit tests don’t know anything about the Android main thread</em></a><em>. (</em><a href="https://twitter.com/molsjeroen"><em>Jeroen Mols</em></a><em>)</em></li></ul><h4>Component Injection</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*roeSfi_Q7db7N-i-FB1K0A.jpeg" /><figcaption><a href="https://www.flickr.com/photos/soldiersmediacenter/16327158833/in/photolist-qSLXuv-o1GVxr-o1UweQ-o9L5ZX-nU9GL4-nJvGLb-5K7CDr-jMs8Bn-nUmVGU-ee6xR3-a8711K-8VJB2N-6JHmQK-nigFUx-o9M69k-stPUhw-nK4xuD-pQNRSY-o5eK2k-nFJQMo-nDJJic-mhSRmc-nYYTBN-nULwRJ-nQqGtt-mgYaV4-pT3Am3-4N4zVZ-o7V3P1-f66vm2-kfVCoy-owjBWP-nEp8HA-nMLf7t-rPiKXz-nJx4xY-f66Wga-o71MXx-sGNNFm-nRTZZ9-or3vmE-nqxPyW-o1eDqz-nJwP9D-o322zr-oW81Yy-e5CWsE-d4kMoJ-8cGajX-91CD6C/">The U.S. Army</a></figcaption></figure><p>Reusing components in JUnit 5 cleans up code and improves efficiency. ParameterResolver is implemented in <em>ContentTestExtension.kt</em> (<strong>Step 1a</strong>), then supportsParameter and resolveParameter are implemented to check for the type of component being requested for injection in <em>FeedLoadTests.kt.</em> Lastly, appropriate component is returned to the test.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9b3126358cb4dd25f0d92d8e3a78c2e1/href">https://medium.com/media/9b3126358cb4dd25f0d92d8e3a78c2e1/href</a></iframe><ol><li><em>FeedLoadTests.kt </em>requests a CoroutineDispatcher and ViewModel object in the class constructor.</li><li>supportsParameter<strong> </strong>checks for the type of object being requested for injection.</li><li>resolveParameter returns the corresponding object based on the requested object type.</li></ol><p><em>See </em><a href="https://www.baeldung.com/junit-5-parameters"><em>Inject Parameters into JUnit Jupiter Unit Tests</em></a><em> (</em><a href="https://www.baeldung.com/"><em>Baeldung</em></a><em>)</em></p><p><strong>Store Objects</strong></p><p>To reuse components for injection that have already been created JUnit 5’s getStore is used to save and retrieve components.</p><p>4. getTestCoroutineDispatcher and getViewModel retrieve the object based on the key/value stored using get. If the value is null in resolveParameter a new instance is created, otherwise the existing instance is returned from storage.</p><p>5. saveAndReturnTestCoroutineDispatcher saves the object based on a key/value using put.</p><p><em>See </em><a href="https://junit.org/junit5/docs/current/user-guide/#extensions-lifecycle-callbacks-before-after-execution"><em>5.8.1. Before and After Test Execution Callbacks</em></a><em> (</em><a href="https://junit.org/junit5/docs/current/user-guide/"><em>JUnit 5 documentation</em></a><em>)</em></p><h3>1b. Configuration — Parameterized Tests</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/324/1*VKp3xe8hGi30TjG_RzC6gA.gif" /><figcaption>FeedLoad view event error fired in ‘onCreate’ of Android lifecycle</figcaption></figure><p>Parameterized tests in JUnit 5 make testing error responses, empty/null data, and other edge cases easier by creating a stream of test objects modeling different scenarios that are run with the same test logic. This is important to avoid unintended consequences in production.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/41f6bfe03113ed02391ed7169358aa17/href">https://medium.com/media/41f6bfe03113ed02391ed7169358aa17/href</a></iframe><h3>2. Mock Components</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tDTRNQdmZJ3yMNjUv1eP1w.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@bockman?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Darren Bockman</a> on <a href="https://unsplash.com/s/photos/train%20set?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>As the tests are focused on the ViewModel&#39;s view events, state, and effects, the components and data responses from the network and local database need to be mocked, as they will not be tested directly.</p><h4>Objects</h4><p><em>ContentRepository.kt </em>is a Singleton object, responsible for returning data responses from the network and database requests to the ViewModel business logic, that updates the view state/effects.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6b249e476ef293f325eacbbe22af0b76/href">https://medium.com/media/6b249e476ef293f325eacbbe22af0b76/href</a></iframe><p><strong>1. Mock object creation.</strong></p><p>Since the Repository is used for every test it will be created once in the <em>ContentTestExtension.kt</em> with MockK’s mockkObject method, before all of the tests run. Then, the Repository will be returned to its’ original state after all of the tests have completed with unmockkAll.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e9f89010c316b5164d0bf8e7f420cab5/href">https://medium.com/media/e9f89010c316b5164d0bf8e7f420cab5/href</a></iframe><p><strong>2. Define mock return values.</strong></p><p>Within the test, the mock components used are initiated before the view events are fired or assertions made.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dc45e42c9e0aace8332f88a6d3abf233/href">https://medium.com/media/dc45e42c9e0aace8332f88a6d3abf233/href</a></iframe><p>mockComponents will return the mocked Repository data in the same format it is returned in production in order to test the ViewModel&#39;s business logic (view/effects).</p><ol><li>The exact method in the Repository, getMainFeedListis defined inside coEvery so that MockK can listen for this method to be called, and return the corresponding test data instead of using the production Repository.</li><li>coEvery is used for Coroutines here, otherwise use every</li></ol><p><em>See </em><a href="https://stackoverflow.com/questions/50435770/how-do-i-create-a-pagedlist-of-an-object-for-tests/57794102#57794102"><em>Convert List Into PagedList With Mock DataSource.Factory</em></a><em> (StackOverflow)</em></p><h4>Static Methods</h4><p>Coinverse’s written code is 100% in Kotlin, however libraries included contain Java static methods that are called within libraries implemented in the Repository and require mocking.</p><p><strong>Production</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0d16eb620472b38c6b15416fbe332538/href">https://medium.com/media/0d16eb620472b38c6b15416fbe332538/href</a></iframe><p><strong>Test</strong></p><p>To mock <em>FirebaseRemoteConfig’s </em>static method, mockkStatic is used. Because we’re not testing <em>FirebaseRemoteConfig.class</em>, the class is returned empty using relaxed = true.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dcd0764197823d94116d2d9042b931cc/href">https://medium.com/media/dcd0764197823d94116d2d9042b931cc/href</a></iframe><h4>Constant Values</h4><p><strong>Production</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/46434d9ae3b58823493009553e316c0d/href">https://medium.com/media/46434d9ae3b58823493009553e316c0d/href</a></iframe><p>To mock static values mockStatic is also used, but the package name that Java converts the Kotlin code into is required for Mockk to properly read the value called in the test and return a test value.</p><p><strong>Test</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/537c59902520820a1bbda530be65bdec/href">https://medium.com/media/537c59902520820a1bbda530be65bdec/href</a></iframe><h3>3. Create View Events</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nHikGp24dXQmML9zNcPUPA.jpeg" /></figure><p>We begin testing the ViewModel by calling the view event FeedLoad, a Kotlin <a href="https://kotlinlang.org/docs/reference/sealed-classes.html">Sealed class</a> of type ContentViewEventType.</p><p><strong>Test</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ed6538128f002feb84993676efc0a688/href">https://medium.com/media/ed6538128f002feb84993676efc0a688/href</a></iframe><p><strong>Production</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/962a4b977840c532bbafee243f18f2c4/href">https://medium.com/media/962a4b977840c532bbafee243f18f2c4/href</a></iframe><p>Within the ViewModel this will populate related view state and effects.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0c824877825109e9483f987214270ade/href">https://medium.com/media/0c824877825109e9483f987214270ade/href</a></iframe><h3>4. Make Assertions</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/324/1*0CDrPqbWDiZphuICkH83JA.gif" /></figure><p>After the FeedLoad event is called in the ViewModel we test the view’s persisted data (view state) containing the main feed content, and the corresponding view effects initiated.</p><p>In AssertJ the actual value resulting from the test is passed into assertThat, and the expected value is passed into isEqualTo.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b8d7896aca230045009b46a8e904d157/href">https://medium.com/media/b8d7896aca230045009b46a8e904d157/href</a></iframe><p>1. To run Coroutines synchronously testDispatcher.runBlockingTest is used.</p><p><em>See </em><a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/#runblockingtest">kotlinx.coroutines</a><a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/#runblockingtest">runBlockingTest</a> (Documentation)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SrlBtO5PMo4x_V8v9Fvlrw.png" /></figure><p>2. Both the view state value pagedList and view effect UpdateAds are being observed and tested with the utility function getOrAwaitValue.</p><p><em>See </em><a href="https://medium.com/androiddevelopers/unit-testing-livedata-and-other-common-observability-problems-bb477262eb04"><em>Unit-testing LiveData and other common observability problems</em></a><em> (</em><a href="https://twitter.com/ppvi">Jose Alcérreca</a><em>)</em></p><p>3. I’ve also created utility extension functions for observing LiveData from the view state and effects with feedViewState, viewEffects, and observe.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/03a178dc533cf85df454f095f4e0c875/href">https://medium.com/media/03a178dc533cf85df454f095f4e0c875/href</a></iframe><h4>Error</h4><p>When a JUnit 5 parameterized tests fails with AssertJ, Android Studio produces the test conditions for the failure and the expected/actual result in order to debug the test.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8b9Vd2YT2W_4Q6EsTSrqcw.png" /><figcaption>Test case conditions</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WVHHCHqWr8ZP0FDwUOB4Fw.png" /><figcaption>Test error with expected/actual results</figcaption></figure><h3>5. Verify Tests</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jvlfyIhjJrTsdZqpWcAacA.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@spacex?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">SpaceX</a> on <a href="https://unsplash.com/s/photos/spacex?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>The last step in testing is to use Mockk to verify that all of the expected methods are called and also that all of the methods called during the tests are expected.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3b7332e5b2443e3de1c0f5a91e423aa9/href">https://medium.com/media/3b7332e5b2443e3de1c0f5a91e423aa9/href</a></iframe><ol><li>coVerify is used for code containing Coroutines, otherwise verify would be used.</li><li>The verification confirms that the method getMainFeedList is called within the tests.</li><li>confirmVerified checks if all method calls fired in the tests are verified/expected to be made.</li></ol><h4>Errors</h4><p>Two common errors with verification are methods called in the tests that aren’t verified, and methods that are expected to be verified, but never called in the tests.</p><p><strong>Method called in test, but not verified</strong></p><p>This could either occur when you forget to include a method in the verification, or if an unexpected method is being called in the test that should not be.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T1Liuz4Yg9DMssbwYtzFYg.png" /><figcaption>Not verified calls</figcaption></figure><p><strong>Method not called in test, but should be verified.</strong></p><p>This indicates the test code is likely not running as expected as the method should be accessed, but is not.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i2H5Le4596B6ngED7IrzEw.png" /><figcaption>Method was not called</figcaption></figure><h3>Recap</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*s4p6MKrn3MCVLM5RVQ0iTQ.jpeg" /></figure><blockquote>Using the modular approach with local unit tests, 96% of the lines of code for the ContentViewModel are covered.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dXp_OVJ_FLmRB14PWjlx3A.png" /></figure><h4>Takeaways</h4><ol><li>Modularity improves testability.</li><li>80% of work consists of configuration (Coroutines, LiveData, component injection, test cases) and mocking.</li><li>Ensure view state/effects are tested in the correct order they occur in production.</li><li>Ensure emission of all of the view states/effects scenarios are tested.</li><li>Be careful one test does not break the following parameterized tests.</li></ol><h4>Next Steps</h4><ol><li>🧪 Expand local unit tests to the entire app.</li><li>🏗️ Integrated (dependent components) tests, <em>i.e. Repository</em>.</li><li>⚙️Instrumented (e2e) tests, <em>i.e. View, ViewModel, Repository</em>.</li><li>📱Espresso UI tests, <em>i.e. View</em>.</li></ol><h3>Resources</h3><ul><li><strong>Check out Coinverse on the Play Store:</strong></li></ul><p><a href="https://play.google.com/store/apps/details?id=app.coinverse">Coinverse - cryptocurrency news, bitcoin, ethereum - Apps on Google Play</a></p><ul><li>Explore and run the Coinverse code:</li></ul><p><a href="https://android.jlelse.eu/sample-app-android-unidirectional-data-flow-b9f8ad0fbca3">Sample App — Android Unidirectional Data Flow</a></p><ul><li><a href="https://docs.google.com/presentation/d/1W2WxXE-9YLGg4uwgXMB7eEOYx9vCiR5kMo799sH_uzU/?rm=minimal">Slides</a>:</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fembed%3Fid%3D1W2WxXE-9YLGg4uwgXMB7eEOYx9vCiR5kMo799sH_uzU%26size%3Dl&amp;url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F1W2WxXE-9YLGg4uwgXMB7eEOYx9vCiR5kMo799sH_uzU%2Fedit&amp;image=https%3A%2F%2Flh5.googleusercontent.com%2F7igEd0zQ1aVdPrsCe9AYFP5sa4FXMh0XyTo_Ul4BnW8-bG44OqKcHIOhHz8nMoApA5tR0Q%3Dw1200-h630-p&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=google" width="700" height="559" frameborder="0" scrolling="no"><a href="https://medium.com/media/eb00afc33bf645066150c15abf97a5f0/href">https://medium.com/media/eb00afc33bf645066150c15abf97a5f0/href</a></iframe><ul><li>Notes: <a href="https://docs.google.com/document/d/1vmOpCjw7MekFwD5ACNlByE69kYdMBXuzr-AViO79s2Y?rm=minimal">MockK</a>, <a href="https://docs.google.com/document/d/1pKyOklYCHGW5uthg6ZHzBCPGSzjv7cJeBjRCfn5Eqt8?rm=minimal">JUnit 5</a></li></ul><p>A <strong>big thanks</strong> to <a href="https://twitter.com/droidcon">DroidCon</a> for providing the opportunity to share the above at the 2019 San Francisco conference! 🙏</p><h4>I’m Adam Hurwitz, an Android consultant + developer. <a href="https://medium.com/@adamhurwitz">Follow me on Medium</a> for more on Android and <a href="https://www.linkedin.com/in/adamshurwitz/">connect with me on LinkedIn</a>.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6qxkrWJ4Bn00fzy5uwfcVQ.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=487a6e6f5c9" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/android-unidirectional-data-flow-local-unit-testing-487a6e6f5c9">Android Unidirectional Data Flow — Local Unit Testing</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>