<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Agustín Tomas Larghi</title>
    <description>The latest articles on DEV Community by Agustín Tomas Larghi (@4gus71n).</description>
    <link>https://dev.to/4gus71n</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F332452%2Ff0798b13-d1a0-4951-af83-91d4202278d8.png</url>
      <title>DEV Community: Agustín Tomas Larghi</title>
      <link>https://dev.to/4gus71n</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/4gus71n"/>
    <language>en</language>
    <item>
      <title>Oof – look, that's a legacy codebase!</title>
      <dc:creator>Agustín Tomas Larghi</dc:creator>
      <pubDate>Sun, 16 Jul 2023 22:26:12 +0000</pubDate>
      <link>https://dev.to/4gus71n/oof-look-thats-a-legacy-codebase-2aco</link>
      <guid>https://dev.to/4gus71n/oof-look-thats-a-legacy-codebase-2aco</guid>
      <description>&lt;p&gt;If you are an engineer who has been in the industry for over nine or ten years, you will likely have experienced projects that are not "in such great shape".&lt;/p&gt;

&lt;p&gt;You know what I'm talking about. Those projects where the first engineer decided to implement an MVP architecture based on some random article they skimmed from Medium about using one Activity and custom views for each screen.&lt;/p&gt;

&lt;p&gt;Then, when the Jenga tower was shaking on the brink of collapse, the engineer figured it was time to "go looking for a new adventure" thus, a new developer had to be hired!&lt;/p&gt;

&lt;p&gt;The new dev figured that the previous one had no idea what they were doing, so they decided to flip things over – this time we go with a custom BottomSheetDialogFragment for each screen. Surely this time it'll work!&lt;/p&gt;

&lt;p&gt;It didn't.&lt;/p&gt;

&lt;p&gt;And yet again, this engineer had to go looking for new challenges. Repeat this 5 times over a period of 5 years and you no longer have a piece of software — you have a ticking bomb. You have a Coke that will explode in your hand as soon as you grab it from the shelves and leave you with a face full of shrapnel.&lt;/p&gt;

&lt;p&gt;Now it is &lt;strong&gt;your&lt;/strong&gt; turn. I will share a few things I have learned to help you deal with these situations. Hopefully, some of these tips might come in handy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remove unused resources
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You go through the codebase and more often than not see pieces of code that are completely grayed out, unused. There is no such thing as keeping code "just in case", you either use the code or you don't – keeping leftovers there is just going to confuse people, and it is probably a symptom of a deeper issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Android Studio has a "Remove unused resources" tool under Refactor&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpfsvlgak2wh0i4pgzn4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpfsvlgak2wh0i4pgzn4.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can preview what the removal of the unused resources would look like and exclude some of those deletions if you don't feel comfortable enough with some of the things Android Studio picks.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR templates
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, the QA engineers complain that every fix your team submits is like a butterfly effect: it creates 3 new different bugs, or they say the fix/feature isn't even working at all. This is what I call late-stage "I don't give a flying fig" syndrome. The code base is so fragile and so prone to bugs that no one even cares to test stuff, or even run those changes locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set a PR template for the team. It is not necessary to use a super-long annoying-to-fill form, just put two items to complete every time an engineer wants to submit a PR. A short description of what's in there, and a video walkthrough of the engineer explaining the changes and showing how the feature works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmigg30ik151ng65pcjf8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmigg30ik151ng65pcjf8.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You know Slack lets you record and download videos? &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjfwxdg5umhvaoj1mkl2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjfwxdg5umhvaoj1mkl2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use that tool to record videos, then download them, and drag-and-drop them into the PR description. It's a piece of cake, and won't take more than 5 minutes. But if your engineers can't even find the time to whip up a 5-minute video to explain their work, well, that's a whole other cake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kotlin basics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You go through the code and see things like mutable data types being used on re-assignable (mutable) variables&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var someLiveData = MutableLiveData&amp;lt;Something&amp;gt;()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or &lt;/p&gt;

&lt;p&gt;&lt;code&gt;var someCollection = mutableListOf&amp;lt;Something&amp;gt;()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or maybe you see data class structures abusing &lt;code&gt;var&lt;/code&gt; properties&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data class Something(
  var prop1: SomethingElse?,
  var prop2: SomethingElse?
  // ...
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is usually a symptom of engineers who haven't fully migrated from Java to Kotlin. It is important to fully understand concepts like mutability and nullability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a symptom of a lack of Kotlin basics. I'd like to defer to a super-helpful article I read some time ago:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.javacodegeeks.com/starting-with-kotlin-cheatsheet" rel="noopener noreferrer"&gt;https://www.javacodegeeks.com/starting-with-kotlin-cheatsheet&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Soup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Alrighty, in different corners of the app, you have screens that are either using ViewBinding, Kotlin Synthetics, Butterknife (because why not?), or Composables.&lt;/p&gt;

&lt;p&gt;Some screens have the ViewModels communicate back to the UI using either LiveData, Flow, or Kotlin Channels, and some are using DataBinding.&lt;/p&gt;

&lt;p&gt;Every engineer that passed through this project had their own unique genius way of doing things... because the guy before them was a complete idiot, obviously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Try to find a balance between what &lt;em&gt;the team&lt;/em&gt; thinks the best practices are and what you already have there in the codebase. Schedule either weekly or bi-weekly meetings with the engineering team, go layer by layer through the architecture and decide what the best practices are going to be from now on. For example:&lt;/p&gt;

&lt;p&gt;"Okay, we use Retrofit for the API calls, do we wrap the responses on a &lt;code&gt;Response&lt;/code&gt; wrapper to carry over the status response or we don't care about it?"&lt;/p&gt;

&lt;p&gt;"Okay, how do we have ViewModels communicate to the UI from now on? We have all these options, what do we use and why?"&lt;/p&gt;

&lt;p&gt;"Okay, do we use Jetpack Compost or do we use something else as a UI framework?"&lt;/p&gt;

&lt;p&gt;You go layer by layer, figure out what the available options are (please don't thrust yet another tool into the mix!), and pick one. If possible, document that somewhere, either Notion or a wiki or whatever, but do document it – agreeing on something and not writing it down is the same as doing nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation Soup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's like a choose-your-own-adventure book...except in the land of app navigation! Some parts of the app are using Navigation Graphs. Some are going for the one Activity, many custom views approach. Some are full-screen DialogFragments. Others are full-height BottomSheetDialogFragments. What will you choose for your app journey?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same as above, sync with the rest of the team and pick one solution. I'd recommend that whatever you decide, you use Fragments. Think about all the third-party libraries out there that provide some sort of UI; they all do it either through Activities or Fragments. The goal for each flow of your app (the login flow, the account creation flow, the onboarding flow, etc.) should be exactly that — expose each of those flows as if you were sharing an SDK that would be used by other developers.&lt;/p&gt;

&lt;p&gt;Of course, this requires a bit more technical brainstorming depending on the spaghetti you have at hand – I'd recommend that if the app has a base class for each screen (those awesome noodles of wisdom) you keep using them. Hear me out:&lt;/p&gt;

&lt;p&gt;Let's say your app works using &lt;code&gt;BaseSomethingFragment&lt;/code&gt;, each screen extends on this base class. Internally, &lt;code&gt;BaseSomethingFragment&lt;/code&gt; extends from &lt;code&gt;BottomSheetDialogFragment&lt;/code&gt; and it tweaks it so it prompts at full height.&lt;/p&gt;

&lt;p&gt;Something you could do is to tweak this base class so it has a &lt;code&gt;FragmentViewContainer&lt;/code&gt; and it uses a Navigation Graph internally, that way you can create the rest of the screens using Fragments, and eventually move every screen into bare Fragments instead of using that base class. &lt;/p&gt;

&lt;p&gt;Just an idea, it's not a foolproof plan, but it'll give you the chance to do things one step at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Isolate new code through modules
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Given that the app is one large monolithic &lt;code&gt;:app&lt;/code&gt; module, containing all the above items, how can we begin to implement our fresh approaches and start to separate the components?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sunset the APP module. All the apps out there can be divided into different flows. For example, when the user goes through the login screens, that's the login flow – it might be a collection of 2, or 3 screens, but that's a self-contained flow. You might also have an onboarding flow, account creation flow, etc.&lt;/p&gt;

&lt;p&gt;Each of these flows should be contained in a feature module (just a fancy name for a library module) and each of these feature modules should be completely independent of each other so we prevent dependency-spaghetti. You can do this step by step:&lt;/p&gt;

&lt;p&gt;First, you have your monolithic app module&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fae9n73zcco5jeg2lt9ad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fae9n73zcco5jeg2lt9ad.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, just to keep things tidy let's rename that module into &lt;code&gt;:app-legacy&lt;/code&gt; just everyone knows that we aren't supposed to keep adding things in there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nzli3wuptdkx7j1d0rx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nzli3wuptdkx7j1d0rx.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are going to create a new application module, namely &lt;code&gt;:company-app&lt;/code&gt; and have it depend on the old &lt;code&gt;:legacy-app&lt;/code&gt; module. We have also turned the &lt;code&gt;:legacy-app&lt;/code&gt; module from an application module into a library module.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1eqp0g4tgh5xyywrzzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1eqp0g4tgh5xyywrzzw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most likely, your old &lt;code&gt;:legacy-app&lt;/code&gt; module had an &lt;code&gt;Application&lt;/code&gt; class – in situations like these, the &lt;code&gt;Application&lt;/code&gt; class is usually highly bloated with code. So, to start fresh you can have your new &lt;code&gt;:company-app&lt;/code&gt; application module have its own &lt;code&gt;Application&lt;/code&gt; class that extends from the old one. You can also mark the old &lt;code&gt;CompanyApp&lt;/code&gt; as &lt;code&gt;@Deprecated&lt;/code&gt; to prevent further things from being added in there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtp6cinp818vxe96klt5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtp6cinp818vxe96klt5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might also have to move many plugins from the &lt;code&gt;:legacy-app&lt;/code&gt; down into the new &lt;code&gt;:company-app&lt;/code&gt; application module. For example, if you are using Firebase and &lt;code&gt;google-service.json&lt;/code&gt; you might have to move these down there, as they need to be plugged into the application module.&lt;/p&gt;

&lt;p&gt;Now you can create completely independent feature modules to either refactor existing flows or create new ones. Let's say we had an account creation flow that was rid of bugs and we want to extract it out into an isolated module so we can refactor things safely.  &lt;/p&gt;

&lt;p&gt;First, we create a new library module for this new flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbileexfgacdoy4t3x8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbileexfgacdoy4t3x8g.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we move all the classes, all the &lt;code&gt;Fragments&lt;/code&gt;, all the stuff specific to that flow into that new library module. If something is reused between flows, let's say you have a &lt;code&gt;SharedPreferences&lt;/code&gt; wrapper, that stays up in the &lt;code&gt;:app-legacy&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;What about if we want to navigate between feature modules? Let's say we have another feature module for another flow of the app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefn3rczvamsqrxsehiic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefn3rczvamsqrxsehiic.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, you can use a Navigation class. You can declare an interface up in the &lt;code&gt;:app-legacy&lt;/code&gt; that exposes the entry points for each of the screens in each flow. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface MyNavigationProvider {
   fun getLoginFragment(context: Context): `BaseSomethingFragment`
   fun getUserProfileDetail(context: Context): Intent
   // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can provide either Fragments or Intents, whatever fits your architecture best.&lt;/p&gt;

&lt;p&gt;This interface would be implemented in the &lt;code&gt;NewCompanyApp&lt;/code&gt; Application class, and since the &lt;code&gt;:company-app&lt;/code&gt; module ties all the feature modules together it knows about all these classes.&lt;/p&gt;

&lt;p&gt;If you ever need to jump from one screen in one feature module to another, the only thing you need to do is to cast the application context into the &lt;code&gt;MyNavigationProvider&lt;/code&gt; interface that was declared up in &lt;code&gt;:app-legacy&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Something like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getApplicationContext() as? MyNavigationProvider&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;and that's it!&lt;/p&gt;

&lt;p&gt;Now, don't think the job is done yet. This is the first step on a long road. Eventually what I try to do is turn the &lt;code&gt;:app-legacy&lt;/code&gt; into a &lt;code&gt;:core&lt;/code&gt; module, with just the things that are common to all feature modules. I try to extract the UI and everything else down into feature modules.&lt;/p&gt;

&lt;p&gt;But this, at least, should be enough to provide your team with a way to create new flows independently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5tvhrh9d03e5nqm8z9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5tvhrh9d03e5nqm8z9m.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many things you can do after this, you can extract all your base UI into a &lt;code&gt;:design-system&lt;/code&gt; library module, so you encapsulate the look-and-feel of your app in one single place, &lt;em&gt;but&lt;/em&gt; I would strongly recommend you first get the &lt;em&gt;basics&lt;/em&gt; working, then you can start thinking about adding extra stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set a coding standard
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every PR someone submits seems to have more changes due to code formatting than actual substance - it's like a one-line bugfix needs 30 changes; one for the bug and 29 for imports turning into wildcard imports or tab-formatting. Who knew code formatting could be so complicated?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Either push some of the &lt;code&gt;.idea&lt;/code&gt; files up into the repo, or share the code style configuration across the team.&lt;/p&gt;

&lt;h3&gt;
  
  
  That's all
&lt;/h3&gt;

&lt;p&gt;Finally, set expectations with everyone in the team, engineers and uppermanagement, lay all the things on the table so everyone is aware of what's going on and what needs to be fixed.&lt;/p&gt;

&lt;p&gt;I'm sure most people will cry "micromanagment!" at some of my proposals, but hey, to each their own – you like to push code up and pretend it's working, that's your prerogative. That's not my thing.&lt;/p&gt;

</description>
      <category>android</category>
      <category>refactoring</category>
      <category>kotlin</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Performance Pyramid (part 4) – Team Management</title>
      <dc:creator>Agustín Tomas Larghi</dc:creator>
      <pubDate>Wed, 05 Apr 2023 19:21:50 +0000</pubDate>
      <link>https://dev.to/4gus71n/the-performance-pyramid-part-4-team-management-2kl5</link>
      <guid>https://dev.to/4gus71n/the-performance-pyramid-part-4-team-management-2kl5</guid>
      <description>&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/4gus71n/the-performance-pyramid-part-1-communication-2029"&gt;The Performance Pyramid (part 1) – Communication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Performance Pyramid (part 2) – Ownership [TBD] &lt;em&gt;I'll talk about the "Ownership" boulder&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The Performance Pyramid (part 3) – Tech Skills [TBD] &lt;em&gt;I'll talk about the "Tech Skill" boulder&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Performance Pyramid (part 4) – Team Management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The Performance Pyramid (part 5) – Remote Culture [TBD] &lt;em&gt;I'll talk about remote working; how to manage time-zone differences, working with people from different cultures, and how to efficiently work with people from all around the world&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  About
&lt;/h3&gt;

&lt;p&gt;Some management books encourage people to build relationships with your teammates so you can provide better, more honest feedback. They say that if you don't connect with your teammates, you won't be able to provide feedback that they actually listen to. I disagree with this. I have tried to do that many times before, and it has never worked. No one is going to get along in a team where they have repeatedly missed deadlines, where they have repeatedly misinterpreted requirements, where communication is next to none – if you want your team to get along, first, they need to start winning. &lt;/p&gt;

&lt;h4&gt;
  
  
  Shrinking egos
&lt;/h4&gt;

&lt;p&gt;A team's issues and dysfunction stem from egos. A successful team needs everyone, from individual contributors to the CTO, to completely humble themselves. This is no easy task; first, we need to understand why there are such big egos in the software engineering industry. I have seen that many engineers out there make programming their primary personality trait. They are the cool nerds, the savvy engineers, so even the smallest amount of criticism would make them feel personally attacked&lt;/p&gt;

&lt;p&gt;How can we fix this problem without making it worse? Here are some ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Be humble in front of everyone&lt;/strong&gt;. Humility is the best antidote against big egos. Ask for help. Tell everyone that you don't know what a certain topic is about. If there's a technical challenge that you need to face, make it clear that you don't know what to do. Engineers aren't a limitless knowledge repository with all the algorithms and solutions hardcoded into their brains, make this clear to everyone. People with big egos might notice this behavior and lower their defenses a little.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Publicly thank people for their help and their work&lt;/strong&gt;. You might think that doing things like this might inflate people's egos even more, but, on the contrary, more often than not, people's egos get inflated because they feel they don't get the recognition and praise they deserve. So they push even harder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pay attention to the tone of every conversation&lt;/strong&gt;. Even if an engineer is right about a technical decision, if they communicate their arguments in a way that is hurtful or spiteful, that needs to be addressed. Usually, having that person read out loud what they wrote during a private 1:1 meeting, is enough to make them realize their mistake. Make sure you point out that you do appreciate their passion for their jobs and the attention to detail, but that they need to be careful about how they communicate things.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Balancing opinions
&lt;/h4&gt;

&lt;p&gt;Software engineering is a creative profession, and oftentimes team members will have different opinions about how to solve a problem. It is important to be mindful of how these situations are handled in order to create a positive work environment.&lt;/p&gt;

&lt;p&gt;For example, let's say you have two engineers who have different opinions about how to solve an issue; how do you unblock this situation? &lt;strong&gt;First&lt;/strong&gt;, ask if they have any technical proof to back up their arguments; many times, engineers push an idea just because they "feel" it is the right thing to do. &lt;strong&gt;Second&lt;/strong&gt;, try to bring everyone back down to earth, remind everyone that we are working as a team and that sometimes we have to compromise and accept other people's ideas just as they accept ours. &lt;strong&gt;Third&lt;/strong&gt;, everyone should get a vote; the best solution is whatever the engineering team accepts as a whole. &lt;strong&gt;Fourth&lt;/strong&gt;, never shoot down someone else's ideas. You may have to deal with situations in which no argument is enough to convince someone about a technical decision, even when everyone else has agreed to it. In these situations, suggesting to "put a pin in it" or "revisit it later" can keep the conversation going.&lt;/p&gt;

&lt;h4&gt;
  
  
  Meeting etiquette
&lt;/h4&gt;

&lt;p&gt;Every day, more and more companies are moving to a remote-first model. It is essential to have a policy during online meetings so they don't turn into an annoyance for everyone involved.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make yourself presentable&lt;/strong&gt;. This might be a common requirement for experienced remote workers, but for most people who haven't worked remotely until the COVID pandemic, this isn't so obvious. Don't join a meeting wearing a robe. Don't join a meeting from your bed. Always keep your webcam on so other people know you're paying attention. I understand that some people are shy about turning their cameras on and having eyes on them when they speak, but if that's the case, you can just switch tabs. Switch to another tab where you don't have to look at the people in the meeting, and that's it. And please don't attend a meeting shirtless – yeah, I've seen that before.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Filter out the noise&lt;/strong&gt;. If you are in a noisy environment, try to invest in some noise-canceling software or a headset with built-in noise-canceling hardware.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use the chat section&lt;/strong&gt;. If you feel uncomfortable about interrupting people, you can always write something in the chat section or use the "raise your hand" action.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How to encourage good traits
&lt;/h4&gt;

&lt;p&gt;There is no simple answer to this. People normally do not like being told what to do, especially engineers. Ideally, you would want all members of the team to spontaneously embrace these best practices, without needing to enforce any rules, without having to guide or repeat the same thing time and again. That's not the case most of the time. Let me share some ideas that might help you see things in a different light: &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;"Broken Windows"&lt;/strong&gt; theory states that places with visible signs of crime – like loitering, vandalism, jaywalking, etc. – are more prone to crimes than places without those signs. Makes sense, right? I mean, if you go to a landfill, you are not going to look for a dumpster to throw the trash in; you are just going to toss it anywhere. The same thing goes for software, it would be unlikely that an engineer stays motivated if they see an untidy code base where no one cares where the trash goes.&lt;/p&gt;

&lt;p&gt;The term &lt;strong&gt;"Bilbao Effect"&lt;/strong&gt; was coined in response to the construction of the Guggenheim Museum in Bilbao. Subsequently, the entire region began to experience a period of growth and increased prestige. The Bilbao effect is quite different from the Broken Window theory. The Bilbao effect proposes that a tidy, pristine building can have a positive impact on the environment around it. If you're leading an engineering team, it's important to remember that showing the way is key to motivating your team. Set the example, push for clean PRs, and be clear in your communication, and your team members will follow. It won't happen overnight, so don't give up if it takes some time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Leadership roles
&lt;/h4&gt;

&lt;p&gt;Many engineers do want to pursue a leadership role at some point in their careers. It might be for good reasons. They might feel comfortable with that kind of role and want to improve their soft skills, or they might feel they are more valuable in coordinating things across different teams rather than being an individual contributor. It might also be for the wrong reasons. They think they are getting too old and it is the next step (even though a good individual contributor is as valuable as a lead) or because they like the big badge that says "lead". I'm going to share a few tips for anyone who is interested in pursuing a leadership role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things that a leader should never do&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Saying that something is "above your pay grade"&lt;/strong&gt;. If you are a leader, your main responsibility is to make sure you unblock your team, and that you provide everything they need to move things forward. They need to implement a feature and we are missing the design? Go and talk with the design team and figure things out. Is there a bug out there in production and we need backend engineers to help? Drag everyone into a meeting and coordinate things. Going through all the usual ceremonies; Backlog grooming, sprint planning, retros, etc. all these things can be very exhausting at times, and on top of that you might have to deal with people's egos, people dropping the ball and not doing things. If at any point doing all this you say: "This is above my paygrade" you have failed as a lead. I understand that there is a limit to everything; it is unreasonable to expect that you do everything. If that ever happens, get into a meeting with whoever you report to and try to figure out how to delegate more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finger pointing&lt;/strong&gt;. Never blame someone for something publicly. When you talk publicly, it is always "we", never "I" or "They". "We" fixed an issue. "We" dropped the ball and introduced a bug. "We" improved the app quality. For better or for worse, we are all in the same boat when we win, and when we lose.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Badge flashing&lt;/strong&gt;. If you ever have to say, "Do it because I'm the lead and I say so," then that's it — find another role. You're not fit to be leading people. I understand that people might be dense, and even if you convey a solid argument with proof that backs up your decision, people might still push back out of spite or sheer stupidity. But there are other ways to solve those kinds of situations. For example, getting another person involved can help you see things from a fresh perspective — maybe you are making a mistake, and you just haven't realized it yet. But never use your authority to close an argument; people will immediately lose any respect they have for you if you do that.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Things that a leader should always do&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Leading by example&lt;/strong&gt;. If you are in a leadership role or aspire to pursue a leadership role, there are things you cannot do. You cannot submit sloppy, untidy, and barely tested Pull Requests. You cannot skip meetings. You cannot go radio silence. If someone asks you something, you need to answer. You should be an example of how all your engineers should perform. It might be exhausting at times, but that's how it is.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Taking notes and sharing your screen&lt;/strong&gt;. Taking notes is an important part of any meeting, but it's also essential that you fully share your screen so everyone can see what you're talking about. If you talk about goals, tasks, or milestones without sharing your screen and showing people what you are talking about, they will very likely forget about what you said or have a hard time trying to follow what you are saying. Put an agenda doc on every Google Calendar event you schedule. This will let everyone put their thoughts out and you will have a document full of actionable items when you're done with the meeting. Meetings can get quite hectic if you don't take notes. For example, let's say you schedule a meeting with your team to talk about the deadlines for a new feature that's going out. Then someone chimes in and says that there's something else they need to do before that, and someone else jumps in, and so on. If you don't keep track of what is needed to be done in a document, by the time you finish the meeting you will have more questions than answers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enforce good practices&lt;/strong&gt;. We have talked about how to diagnose problems in your team, and how to fix them, but it is the job of the leader to make sure everyone keeps up with those best practices. If someone Direct Messages you, tell them to continue that conversation on a public channel. If someone submits a sloppy Pull Request, ask them to fix it. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Letting people go
&lt;/h4&gt;

&lt;p&gt;There's a limit to everything. Many leadership books suggest that you should try to be empathetic with people, connect with them, make them part of your family, etc. However, there is a limit. Sometimes people just don't care. Maybe they're going through personal issues. Maybe they don't feel fulfilled. Who knows? If you have repeatedly expressed your concerns and feedback to one of your engineers about how to improve and they continue to provide lackluster results, if they are showing no signs of improvement, then that person needs to go. It makes other engineers' jobs harder and sets a bad example when under-performers get a free pass.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Performance Pyramid (part 1) – Communication</title>
      <dc:creator>Agustín Tomas Larghi</dc:creator>
      <pubDate>Wed, 05 Apr 2023 19:00:47 +0000</pubDate>
      <link>https://dev.to/4gus71n/the-performance-pyramid-part-1-communication-2029</link>
      <guid>https://dev.to/4gus71n/the-performance-pyramid-part-1-communication-2029</guid>
      <description>&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Performance Pyramid (part 1) – Communication&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The Performance Pyramid (part 2) – Ownership [TBD] &lt;em&gt;I'll talk about the "Ownership" boulder&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The Performance Pyramid (part 3) – Tech Skills [TBD] &lt;em&gt;I'll talk about the "Tech Skill" boulder&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/4gus71n/the-performance-pyramid-part-4-team-management-2kl5"&gt;The Performance Pyramid (part 4) – Team Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Performance Pyramid (part 5) – Remote Culture [TBD] &lt;em&gt;I'll talk about remote working; how to manage time-zone differences, working with people from different cultures, and how to efficiently work with people from all around the world&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About
&lt;/h2&gt;

&lt;p&gt;Despite what movies and TV shows may suggest, the stereotype of the lonely engineer coding in a dark room wearing a hoodie is far from the reality of software engineering. This profession is highly collaborative and requires teamwork and communication skills similar to those of soccer players or medical professionals. A successful engineering team must be on the same page at all times, conducting with clear and effective communication. Every team member must be aware of their role and responsibilities to ensure the project's success.&lt;/p&gt;

&lt;p&gt;This article aims to share a few things I have learned throughout my career as an engineer.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cut the ego
&lt;/h4&gt;

&lt;p&gt;The software industry doesn't need egos, any solution that gets the job done is a fine solution. Everyone should focus on pushing towards the same goal, not on who gets the credit, who gets to shine, or whose PR gets approved. The only thing that matters is getting a &lt;strong&gt;quality product&lt;/strong&gt; out there.&lt;/p&gt;

&lt;p&gt;In this article I'll share practical and actionable tips to lead engineering teams effectively. I'll share my own experiences and also reference books that have been very inspirational. This article is not just a rant about the problems in the industry, but it also provides specific solutions to fix these issues.&lt;/p&gt;

&lt;p&gt;Again, my goal with this article is to provide the tools and strategies you need to lead your engineering team effectively and foster a culture of collaboration and teamwork that drives results.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why should I care?
&lt;/h4&gt;

&lt;p&gt;This article talks a lot about ownership, communication, and how to improve things within your team and organization so you can all achieve the best results and provide quality products to your users, but first, you should ask yourself if you really care or not. Some people don't like ownership; they see a software engineering job as a temporary gig until the next thing comes around. Maybe they are too good for their current company. Maybe they should have been working with John Carmack, but you know, life didn't give them the chance. If you are like that, please don't even bother reading this article. If you are too good for your current team, please re-think your career choices. Because software engineering is a highly collaborative profession and there's no place for misunderstood geniuses.&lt;/p&gt;

&lt;h4&gt;
  
  
  Remote work
&lt;/h4&gt;

&lt;p&gt;I have been working remotely since before the COVID pandemic hit. After the pandemic, remote work became more common, and companies that were reliant on adopting it have come to see its benefits. But I do see that many companies do not know how to manage remote employees. This article will tackle that topic, providing step-by-step instructions on how to educate your resources into a remote work culture. I will share my experience helping companies understand the benefits, pitfalls, and challenges of remote work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The performance pyramid
&lt;/h2&gt;

&lt;p&gt;This is without a doubt the most important and valuable concept that I'll be sharing in this article, the "performance pyramid". This is a visual representation that helps you understand how to improve your team's performance over time by focusing on a few key areas at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_yGGu5Bi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38kx9g0l5gjv5nnq3mat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_yGGu5Bi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38kx9g0l5gjv5nnq3mat.png" alt="Image description" width="289" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Communication&lt;/strong&gt; is the most important boulder in your team's pyramid. Communication is the key to team success. I don't mean that everyone on the team has to speak fluent English. You might have an engineer from South America, Eastern Europe, or Asia who has a thick accent, but he might &lt;em&gt;communicate&lt;/em&gt; better than a native English speaker. What I mean by communication is that your team is able to effectively and efficiently communicate ideas, problems, and solutions to each other through public channels in a transparent and honest way.&lt;/p&gt;

&lt;p&gt;If you see a team where most of the conversations are done through DM, where engineers sweep things under the rug and don't speak up because they are afraid of opening a can of worms, that's a team that has failed to communicate effectively.&lt;/p&gt;

&lt;p&gt;I'll share a few tips to improve communication – turning your engineers into Slack power-users, teaching your engineers how to take notes during meetings, using agenda docs to keep track of action items, how to multitask effectively, learning to mediate between egos and unblock conversations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ownership&lt;/strong&gt; is the second-most important boulder in your team's pyramid. It means that engineers actually care about what they do and they care about the quality of the product they are building. This means that engineers feel accountable for the PRs they submit and that they actually perform some basic testing on the code.&lt;/p&gt;

&lt;p&gt;A team with no ownership of their product is a team where PRs are submitted with no descriptions, no tidiness, single big-bang commits, commented code, and constant back-and-forth with QA because no engineer feels the need to do some basic testing on the code they implement, and even the most basic use case doesn't work as expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech skills&lt;/strong&gt;, surprisingly enough, are the least important boulder in the pyramid. I've seen junior, inexperienced engineers turn into very sharp programmers. I have also seen senior engineers struggle with communication and ownership due to professional baggage.&lt;/p&gt;

&lt;p&gt;I think it's easier to find talented engineers than to make them. I'll share some tips about how to improve your tech interviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Communication
&lt;/h2&gt;

&lt;p&gt;I have seen many talented engineers fail because of communication issues. Sometimes they cannot communicate their ideas well, but more often than not it is because they are too insecure or anxious. Overthinking is common trait among software engineers.&lt;/p&gt;

&lt;p&gt;I have seen engineers get stressed out over having to ping a manager, schedule a meeting, or do an &lt;code&gt;@here&lt;/code&gt; on a Slack channel. I'd like to think that I am very loud and clear on Slack, and no one has ever slapped my wrist for hitting that &lt;code&gt;@here&lt;/code&gt; to get people in sync. No one has ever yelled at me for trying to express my ideas or made me feel stupid for trying.&lt;/p&gt;

&lt;p&gt;Your team will have reached effective communication when they can talk things through without a second thought. There are many steps you need to go through to achieve such a thing. That's why for this communication boulder and for every other boulder in the "Performance Pyramid", we will run a &lt;strong&gt;self-diagnosis&lt;/strong&gt; test to see how well (or not) your team is doing. Based on the results of the self-diagnosis test, you will be able to make the necessary adjustments to improve your team's communication.&lt;/p&gt;

&lt;h4&gt;
  
  
  Self-diagnosis
&lt;/h4&gt;

&lt;p&gt;I want you to underline each bullet item if it applies to your team or if you've seen this happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;More often than not, when you are in a meeting and someone asks a question directed at your team, no one answers until specifically pulled into the spotlight.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;People rarely communicate through public channels in Slack. You suspect that most of the conversations are done through DMs (Direct Messages).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sometimes people don't reply when they are directly &lt;code&gt;@&lt;/code&gt; pinged on Slack. Sometimes they don't even answer that same day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is lack of communication between the Customer Support team and the engineering team. If someone is unhappy with the product, you rarely hear about it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You don't know what the rest of your engineering team is doing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have underlined some of these items, you could use some help improving communication within your team.&lt;/p&gt;

&lt;h4&gt;
  
  
  Actionable items
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Public channels over private conversations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's important to encourage your team to communicate through public channels. If you see that many conversations are happening through Direct Messages rather than through public channels, you should ask the team to stop using Direct Messages for work-related conversations. People should be using Direct Messages only for private issues like health or time off. If people use DM as their default communication channel, that usually leads to other people missing context on things, or not being aware of how things are going. If you hold long technical discussions over DMs, and then you need to include someone else in that conversation, then you have to waste time looping that person in. Instead, if that same conversation was being held on a public channel, you could just point that person to the thread where that conversation was being discussed and that's it. Abusing DMs leads people to sweep things under the rug, or be overly cautious when speaking on a public channel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the &lt;code&gt;@here&lt;/code&gt; to communicate high-pri messages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n2VdKlBu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oiodsuuxlzmwfsls1ppa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n2VdKlBu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oiodsuuxlzmwfsls1ppa.png" alt="Image description" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pVt1PYVL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ea602sipj7zjqhj18sl6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pVt1PYVL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ea602sipj7zjqhj18sl6.png" alt="Image description" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are two examples of incorrect communication: (1) Do not ping the entire channel just to say hi or things like that. (2) If there's a fire, do not doubt about pinging everyone in the channel. Most often than not, engineers get anxious over-thinking if they should communicate something or not, don't overthink, just do it. No one is going to slap your wrist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always ping people directly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wxPg3BkU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j05q9hscitonqjxy0nfp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wxPg3BkU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j05q9hscitonqjxy0nfp.png" alt="Image description" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5yB8pN7i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4esxi8l9zppv7g2xudx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5yB8pN7i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4esxi8l9zppv7g2xudx3.png" alt="Image description" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Slack (or any other communication tool) can become quite a hectic place for people who aren't used to it. People don't actively browse the Slack channels and see what is going on; they act only if pinged directly. So, if you need to communicate with someone, ping them directly on public channels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use threads to keep the conversation flow tidy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s say that someone pings you on Slack, and you jump to the channel to see that you are 30+ messages behind in an ongoing conversation. It is going to be tricky to figure out what’s going on, and on top of that, the conversation might trail off if someone starts talking about something else in that same channel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z6L0sdoL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9yrptxl04jvcfcr6q40s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z6L0sdoL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9yrptxl04jvcfcr6q40s.png" alt="Image description" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But!&lt;/strong&gt; If you keep threaded conversations things might be a bit more manageable 👇 &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hOgSzSWV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vd6ygqiowimi3xaenyol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hOgSzSWV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vd6ygqiowimi3xaenyol.png" alt="Image description" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how that same thread looks when expanded 👇 &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qOKb9JoA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/17fj6uuhxbosgdj8wber.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qOKb9JoA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/17fj6uuhxbosgdj8wber.png" alt="Image description" width="800" height="1388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don't keep the conversation flow tidy on Slack, things can get messy really quickly. Try to encourage your team to create threads for every topic they need to discuss.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The team needs some design adjustments on a new feature? Start a thread about that in the &lt;code&gt;#design&lt;/code&gt; channel. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The team detected an issue and needs to re-prioritize the current work with the Product Manager? Start a thread.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have quite a few different ideas about how to fix an issue or implement a feature and cannot decide which to use? Start a thread in the &lt;code&gt;#engineering&lt;/code&gt; channel.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's also helpful to keep contextualized threaded conversations, so you can link the Slack threads to PRs, JIRA tickets, Azure tasks, Trello cards, or Monday tickets to give the people some context about what's going on or what conversation led to the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using reminders&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I use Slack reminders for most of my day. If someone pings me about an issue, but I don't have time to check it right now, I'll put a reminder to check it in about an hour. If someone reaches out to me after office hours, I'll put a reminder for tomorrow morning. If I ask someone to review something, I'll set a reminder for tomorrow to check on the progress. Not just Slack, but every chat platform has some sort of "snooze" functionality, even Gmail does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uWXbeBq2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdxfqbowdldbr9q8lgac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uWXbeBq2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdxfqbowdldbr9q8lgac.png" alt="Image description" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gNN5PPIh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wosl877yulpo4b5c6rr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gNN5PPIh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wosl877yulpo4b5c6rr6.png" alt="Image description" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Taking notes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QbD83LIE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnp8ute0f85nj8rczgr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QbD83LIE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnp8ute0f85nj8rczgr1.png" alt="Image description" width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How often have you gotten into a Retro meeting and had no feedback to share because you couldn’t remember anything worth sharing? Keeping notes is essential. You can use whatever platform is comfortable for you. I use Notion because I feel it is quite flexible, and it does allow me to keep TODO items and notes everywhere. You can use whatever is comfortable for you, even a Google Doc.&lt;/p&gt;

&lt;p&gt;Anything that isn't work-related; things that you would like to improve in the code base. Feedback that you would like to share with your teammates. Ideas for new features. All that should go into your TODOs, so when you have a 1:1 with someone or have a retro meeting, you'll have plenty to talk about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-team communication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have often seen engineering teams fail to communicate with non-engineering teams. It is important to educate engineers on how to communicate with other non-engineering teams, such as designers or product managers, so they don't use technical terms and mambo-jumbo that no one understands. Instead of talking about UI widgets, talk about "screens" or "pages". Instead of talking about endpoints and API calls, just call it "fetching data" or "sending a message to the server". &lt;/p&gt;

&lt;p&gt;More often than not, people agree or nod their heads to things they know nothing about, out of shame thinking they would look dumb or because they think everyone else already knows what is going on and don’t want to slow down the conversation. So, educate your team to bridge the gap between engineering and non-engineering team communication.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The 500 DON'Ts about Android Development</title>
      <dc:creator>Agustín Tomas Larghi</dc:creator>
      <pubDate>Tue, 21 Mar 2023 23:55:20 +0000</pubDate>
      <link>https://dev.to/4gus71n/the-500-donts-about-android-development-20i8</link>
      <guid>https://dev.to/4gus71n/the-500-donts-about-android-development-20i8</guid>
      <description>&lt;p&gt;&lt;strong&gt;FYI There are no 500 DON'ts in here, just random 8 things I was thinking about. The 500 is a reference to this &lt;a href="https://www.youtube.com/watch?v=NRvaR4fqrYY" rel="noopener noreferrer"&gt;Simpsons&lt;/a&gt; episode.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I decided to do a small post about some of the most common mistakes I have seen during my career as an Android engineer.&lt;/p&gt;

&lt;p&gt;You will see a little bit of everything here, Kotlin, Android SDK, commonly used third-party libraries, etc. 😉&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ .let isn't a replacement for if != null
&lt;/h3&gt;

&lt;p&gt;I have seen many engineers misinterpret what the &lt;code&gt;.let&lt;/code&gt; function is for and use it like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data class EspressoMachine(
    val amountCoffee: Int,
    val hasWater: Boolean
)

fun testFoo(espressoMachine: EspressoMachine?) {
    var canPrepEspresso = false
    espressoMachine?.let {
        canPrepEspresso = it.hasWater &amp;amp;&amp;amp; it.amountCoffee &amp;gt; 0
    }
    if (canPrepEspresso) {
        // Do something else
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;let&lt;/code&gt; scoping function or the &lt;code&gt;let&lt;/code&gt; operator (call it whatever you want, potato-poteto) it &lt;em&gt;lets&lt;/em&gt; you run an algorithm on a variable, it is designed to transform a variable into something else.&lt;/p&gt;

&lt;p&gt;The correct usage would be something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun testFoo(espressoMachine: EspressoMachine?) {
    val canPrepEspresso = espressoMachine?.let { it.hasWater &amp;amp;&amp;amp; it.amountCoffee &amp;gt; 0 } 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple as that, again, &lt;code&gt;let&lt;/code&gt; isn't a replacement for the &lt;code&gt;if&lt;/code&gt; statement. Also, please do not end up in these kind of situations&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun testFoo() {
    foo?.let {
        boo?.let {
            // Just go with a if (foo != null &amp;amp;&amp;amp; boo != null)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So Donnie, please don't abuse the &lt;code&gt;.let&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;None of the scoped-functions are meant to be a replacement for if != null&lt;/p&gt;

&lt;p&gt;The same thing applies to &lt;em&gt;any&lt;/em&gt; other scoped function, &lt;code&gt;apply&lt;/code&gt;, &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;For example, the &lt;code&gt;apply&lt;/code&gt; lets you &lt;em&gt;apply&lt;/em&gt; changes upon an object. For example, if you have something like this 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun testApply(): Intent {
    val someIntent = Intent()
    someIntent.putExtra("foo", 123)
    return someIntent
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can easily change it to something a bit more readable, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun testApply() = Intent().apply {
    putExtra("foo", 123)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So Donnie, please don't go over the top with the scoped functions and the elvis operator.&lt;/p&gt;

&lt;h3&gt;
  
  
  2️⃣ Using RecyclerView instead of LinearLayout
&lt;/h3&gt;

&lt;p&gt;I often see engineers try to create a dashboard-like UI using a &lt;code&gt;RecyclerView&lt;/code&gt;, where each item is a "section" of the dashboard. This does not scale well and it is not the use case for the &lt;code&gt;RecyclerView&lt;/code&gt;. It is far easier, and more scalable to just have a &lt;code&gt;LinearLayout&lt;/code&gt; with a bunch of &lt;code&gt;Fragments&lt;/code&gt; 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"&amp;gt;

    &amp;lt;fragment
      android:name="com.something.company.SectionOneFragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/&amp;gt;

    &amp;lt;fragment
      android:name="com.something.company.SectionTwoFragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/&amp;gt;

    &amp;lt;fragment
      android:name="com.something.company.SectionThreeFragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/&amp;gt;

  &amp;lt;/LinearLayout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you try to implement this using a &lt;code&gt;RecyclerView&lt;/code&gt;, you will have to worry about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The recycling of the &lt;code&gt;ViewHolders&lt;/code&gt;. If one of these "sections" contains an &lt;code&gt;EditText&lt;/code&gt; you will be forced to keep track of that value in the &lt;code&gt;Adapter&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The sheer amount of callbacks to communicate back to the UI.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Donnie, if every item in the &lt;code&gt;Adapter&lt;/code&gt; is going to be different, don't go with a &lt;code&gt;RecyclerView&lt;/code&gt; use a &lt;code&gt;LinearLayout&lt;/code&gt; instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  3️⃣ Mutable and immutable data
&lt;/h3&gt;

&lt;p&gt;I have seen this with people who are moving away from Java. In Kotlin you have two types of variables, read-only variables  (&lt;code&gt;val&lt;/code&gt;), and re-assignable variables (&lt;code&gt;var&lt;/code&gt;). You also have mutable (for example &lt;code&gt;ArrayList&amp;lt;String&amp;gt;&lt;/code&gt;) data, and immutable data (for example &lt;code&gt;Array&amp;lt;String&amp;gt;&lt;/code&gt;) data.&lt;/p&gt;

&lt;p&gt;Let's try to picture a &lt;code&gt;RecyclerView.Adapter&amp;lt;VH&amp;gt;&lt;/code&gt; and think about the data structure we are going to use to model the list.&lt;/p&gt;

&lt;p&gt;If we go with a read-only mutable data structure, we can update the items one way only 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MyAdapter : RecyclerView.Adapter&amp;lt;RecyclerView.ViewHolder&amp;gt;() {

    private val myList = mutableListOf&amp;lt;String&amp;gt;()

    fun updateList(newList: List&amp;lt;String&amp;gt;) {
        myList.apply { 
            clear()
            addAll(newList)
        }
        notifyDataSetChanged()
    }
    // ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we go with a re-assignable variable and immutable data, there's also only one way to update the list 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MyAdapter : RecyclerView.Adapter&amp;lt;RecyclerView.ViewHolder&amp;gt;() {

    private var myList = listOf&amp;lt;String&amp;gt;()

    fun updateList(newList: List&amp;lt;String&amp;gt;) {
        myList = newList
        notifyDataSetChanged()
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, if we go with both, a &lt;code&gt;var&lt;/code&gt; variable and also use mutable data, it kinda defeats the purpose 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MyAdapter : RecyclerView.Adapter&amp;lt;RecyclerView.ViewHolder&amp;gt;() {

    private var myList = mutableListOf&amp;lt;String&amp;gt;()

    fun updateList(newList: ArrayList&amp;lt;String&amp;gt;) {
        myList = newList
        notifyDataSetChanged()
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And also notice that now I'm tied to use &lt;code&gt;ArrayList&lt;/code&gt;🫠&lt;/p&gt;

&lt;p&gt;So Donnie, please use either &lt;code&gt;val&lt;/code&gt; with mutable data or &lt;code&gt;var&lt;/code&gt; with immutable data.&lt;/p&gt;

&lt;h3&gt;
  
  
  4️⃣ Too much &lt;code&gt;lateinit var&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I find that way too many times engineers rely a bit too much on the &lt;code&gt;lateinit&lt;/code&gt; operator, it doesn't always guarantee that your variable will be initialized, it is just re-introducing &lt;code&gt;NullPointerException&lt;/code&gt; as an &lt;code&gt;UninitializedPropertyAccessException&lt;/code&gt; exception. I find it much more safe to go with either using &lt;code&gt;lazy&lt;/code&gt; initialization or just initializing the &lt;code&gt;var&lt;/code&gt; variable with something. For example 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private lateinit var myRecyclerViewAdapter: MyAdapter

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...
    myRecyclerViewAdapter = MyAdapter()
    myRecyclerView.adapter = myRecyclerViewAdapter
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead you could&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private val myRecyclerViewAdapter by lazy {
    MyAdapter()
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...
    myRecyclerView.adapter = myRecyclerViewAdapter
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5️⃣ Checking if context != null
&lt;/h3&gt;

&lt;p&gt;It might come as a surprise to some, but there is a whole &lt;code&gt;requiresSomething()&lt;/code&gt; API out there for &lt;code&gt;Fragments&lt;/code&gt; and &lt;code&gt;Activities&lt;/code&gt; so you don't have to null-check things that are late initialized, such as the context. For example:&lt;/p&gt;

&lt;p&gt;If you are doing something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TestFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        someButton.setOnClickListener { 
            if (context != null) {
                startActivity(Intent(context, SomeActivity::class.java))
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can instead do something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TestFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        someButton.setOnClickListener {
            startActivity(Intent(requireContext(), SomeActivity::class.java))
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like this you have the &lt;code&gt;requireActivity()&lt;/code&gt; to get the attached &lt;code&gt;Activity&lt;/code&gt; or the &lt;code&gt;requireView()&lt;/code&gt; to get the root view, quite useful to prompt &lt;code&gt;Snackbars&lt;/code&gt;. Do keep in mind that if the context hasn't been initialized, you will get an exception.&lt;/p&gt;

&lt;p&gt;So Donnie, no need to null-check everything here.&lt;/p&gt;

&lt;h3&gt;
  
  
  6️⃣ Observables, Callbacks, and where to use them
&lt;/h3&gt;

&lt;p&gt;There are two widely-use mechanisms to communicate data between components, Callback interfaces and Observables:&lt;/p&gt;

&lt;h4&gt;
  
  
  Callback
&lt;/h4&gt;

&lt;p&gt;It might be a callback interface like this 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data class EspressoMachine(
    val callback: EspressoMachine.Callback
) {
    interface Callback {
        fun onCoffeeReady(isTooHot: Boolean)
    }
}

fun testCallback() {
    val espressoMachine = EspressoMachine(object : EspressoMachine.Callback {
        override fun onCoffeeReady(isTooHot: Boolean) {
            // Do something when ready
        }
    })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or you might use high-order functions, like this 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data class EspressoMachine(
    private var callback: ((Boolean) -&amp;gt; Unit)? = null
) {
    interface Callback {
        fun onCoffeeReady(isTooHot: Boolean)
    }

    fun setOnCoffeeReady(func: ((Boolean) -&amp;gt; Unit)) {
        callback = func
    }
}

fun testCallback() {
    val espressoMachine = EspressoMachine()
    espressoMachine.setOnCoffeeReady {
        // Do something
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the kind of API that we see in the Android SDK. For example, when you want to set an onclick event 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun testCallback(view: View) {
    // () -&amp;gt; Unit
    view.setOnClickListener { 

    }
    // (View, MotionEvent) -&amp;gt; Unit
    view.setOnTouchListener { v, event -&amp;gt; 
        true
    }
    // (View, Boolean) -&amp;gt; Unit
    view.setOnFocusChangeListener { v, hasFocus -&amp;gt; 

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

&lt;/div&gt;



&lt;p&gt;So let me ask you something; it is a good practice to keep code homogeneous, right? If you approach a code base where they are using &lt;code&gt;ViewBinding&lt;/code&gt; you are not going to start using &lt;code&gt;Butterknife&lt;/code&gt; or &lt;code&gt;Kotlin Synthetics&lt;/code&gt;, right? – Okay, following that same way of thinking, if you create a custom view, you should keep things &lt;em&gt;homogeneous&lt;/em&gt; with the Android SDK, right? That's the idea here.&lt;/p&gt;

&lt;h4&gt;
  
  
  Observables
&lt;/h4&gt;

&lt;p&gt;So we have a quite a few "observables" out there, &lt;code&gt;RxJava&lt;/code&gt;, &lt;code&gt;Kotlin Flow&lt;/code&gt;, &lt;code&gt;LiveData&lt;/code&gt;, &lt;code&gt;MutableLiveData&lt;/code&gt;, the &lt;code&gt;SingleLiveEvent&lt;/code&gt; ugly cousin, the (we-got-it-right-this-time) &lt;code&gt;StateFlow&lt;/code&gt; and &lt;code&gt;ShareFlow&lt;/code&gt; – all these observables address different use cases, some people might argue that you could use them all in the same project, if you use them to address specific use cases, and some people might tell you to just pick one. I'm kinda in the middle. I differentiate between two different use cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1# Use Case: ViewModel &amp;lt;&amp;gt; UI comms&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;You want to communicate data from your &lt;code&gt;ViewModels&lt;/code&gt; back to your &lt;code&gt;Activity&lt;/code&gt;, &lt;code&gt;Fragment&lt;/code&gt; or &lt;code&gt;Composable&lt;/code&gt;, right? You have a few options here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;LiveData / MutableLiveData / SingleLiveEvent: The "old" Android solution, I don't see anything bad about going with this option, but keep in mind that you will have to deal with the SingleLiveEvent issue, a swept-under-the-rug Google solution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;StateFlow / ShareFlow: It would be the solution that Google recommends nowadays, and it addresses the SingleLiveEvent issue AFAIK. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2# Use Case: Exposing an API for UseCases / Interactors / Repositories&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;However you structure your app, I'd like to think that we should not do things like querying the backend API or fetching things from the DB, straight in the ViewModel. Usually, most engineers, create a middle layer, call them Repositories, UseCases, Interactors, whatever you want – basically a wrapper to fetch or submit data. What comes next is to figure out what kind of API these components are going to expose. We have a few solutions out there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;RxJava: You have rx.Observable or rx.Single depending on if your stream is going to emit one (a GET request, for example) or many (actively listening for DB changes) objects. It has a &lt;code&gt;.zip&lt;/code&gt;, &lt;code&gt;.contact&lt;/code&gt;, &lt;code&gt;.merge&lt;/code&gt; API so you can combine streams any way you want. Quite flexible – I did use it a lot before moving to Kotlin Coroutines and Flows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kotlin Flow: You have &lt;code&gt;Flow&lt;/code&gt; for when your stream is emitting multiple items, and you can use simple &lt;code&gt;suspendable&lt;/code&gt; functions for when you expect a one-time response.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a very-brief and utterly-simplified version of the kind of data bus solutions we have out there, and I haven't talked about Kotlin Channles and what's the use case for that. Hopefully by now you can dig that there are &lt;em&gt;two&lt;/em&gt; types of "observables" the ones used to communicate data from the &lt;code&gt;ViewModels&lt;/code&gt; back to the UI, and the one used to expose data from the &lt;code&gt;Repositories&lt;/code&gt; / &lt;code&gt;UseCases&lt;/code&gt;/ &lt;code&gt;Interactors&lt;/code&gt; out to the &lt;code&gt;ViewModels&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So, Donnie please –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't use &lt;code&gt;Callback&lt;/code&gt; interfaces in &lt;code&gt;ViewModels&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Don't use &lt;code&gt;LiveData&lt;/code&gt; as callbacks for custom views.&lt;/li&gt;
&lt;li&gt;Don't use &lt;code&gt;RxJava&lt;/code&gt; observables to expose data from the &lt;code&gt;ViewModels&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7️⃣ Re-using UI
&lt;/h3&gt;

&lt;p&gt;The ways in which you can re-use UI on Android are: &lt;code&gt;Fragments&lt;/code&gt;, Custom Views, the &lt;code&gt;&amp;lt;includes&amp;gt;&lt;/code&gt; tag, and now also &lt;br&gt;
 &lt;code&gt;Composables&lt;/code&gt;. Each of these has a different use case.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If you want to re-use a completely static piece of UI, then you can go with the &lt;code&gt;&amp;lt;includes&amp;gt;&lt;/code&gt; tag. For example, a banner with hardcoded text, or a &lt;code&gt;Toolbar&lt;/code&gt; that's the same everywhere. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you want to re-use a piece of UI that has functionality but the functionality is independent of your business, then you can go with a Custom View. For example, a carousel of &lt;code&gt;ImageViews&lt;/code&gt;, anything that makes you think "oh, I could push this up to GitHub as a lib" can probably be done as a Custom View.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you want to re-use a piece of UI that has functionality and that functionality is closely related to your business, then go with a &lt;code&gt;Fragment&lt;/code&gt;. For example, you prompt a screen during onboarding so the user can invite people to download your app, and you also want to prompt the same screen within the app.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Donnie, don't re use UI using &lt;code&gt;ViewHolders&lt;/code&gt; or anything else.&lt;/p&gt;
&lt;h3&gt;
  
  
  8️⃣ Incorrect usage of LiveData + Coroutines
&lt;/h3&gt;

&lt;p&gt;I have seen some people do things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sealed class State {
  data class Loading(
    val isLoading: Boolean
  ): State()
  data class DataSuccessfullyFetched(
    val someData: Data
  ): State()
  object ErrorFetchingData : State()
}

val state = MutableLiveData&amp;lt;State&amp;gt;() // Or SingleLiveEvent 

viewModelScope.launch(Dispatcher.IO) {
   state.postValue(State.Loading(true))
   val response = someRepository.fetchData()
   if (response != null) {
     state.postValue(State.DataSuccessfullyFetched(response))
   } else {
      state.postValue(State.ErrorFetchingData)
   }
   state.postValue(State.Loading(false))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want the &lt;code&gt;state&lt;/code&gt; property to go through the &lt;code&gt;State.Loading(true)&lt;/code&gt; state, then either &lt;code&gt;State.DataSuccessfullyFetched(response)&lt;/code&gt; or &lt;code&gt;State.ErrorFetchingData&lt;/code&gt; and finally &lt;code&gt;State.Loading(false)&lt;/code&gt;, right?&lt;/p&gt;

&lt;p&gt;Well, what's going to happen is that the &lt;code&gt;state&lt;/code&gt; property is just going to "broadcast" the last set value, &lt;code&gt;State.Loading(false)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The issue here is that you shouldn't be using &lt;code&gt;postValue&lt;/code&gt; – "but if I use &lt;code&gt;setValue&lt;/code&gt; instead I get a background thread error message", that's correct, because you shouldn't be using the &lt;code&gt;Dispatcher.IO&lt;/code&gt; at all, the only thing that needs to run on a "background thread" is the &lt;code&gt;fetchData()&lt;/code&gt; suspendable function, which you created as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;suspendable fun fetchData() = withContext(Dispatcher.IO) {
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we should actually be doing is 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
viewModelScope.launch {
   state.value = State.Loading(true)
   val response = someRepository.fetchData()
   if (response != null) {
     state.value = State.DataSuccessfullyFetched(response)
   } else {
      state.value = State.ErrorFetchingData
   }
   state.value = State.Loading(false)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So Donnie, using &lt;code&gt;Dispatcher.IO&lt;/code&gt; doesn't make things magically run in the background thread, also just adding the &lt;code&gt;suspendable&lt;/code&gt; keyword to a function doesn't make that function run in the background either. There is no reason why we should use the &lt;code&gt;postValue&lt;/code&gt; in the &lt;code&gt;ViewModel&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhwwp2aj0ft9elw1m8vu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhwwp2aj0ft9elw1m8vu.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sorry, I lied, there are no 500 don'ts 😜 – Many of the things I pointed out here are up to personal criteria, you can use a knife as a fork if you want, you would probably still be able to pick up the food, no one can stop you. You might also want to use your underwear as a hat – and some people might even think you are cool! – but I'm not sure people will feel totally comfortable around you 🤷.&lt;/p&gt;

&lt;p&gt;Anywho, hope you liked it! 😉&lt;/p&gt;

</description>
      <category>android</category>
      <category>programming</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>Is Jetpack Compose ready for production development?</title>
      <dc:creator>Agustín Tomas Larghi</dc:creator>
      <pubDate>Wed, 09 Nov 2022 20:07:22 +0000</pubDate>
      <link>https://dev.to/4gus71n/is-jetpack-compose-ready-for-production-development-28o6</link>
      <guid>https://dev.to/4gus71n/is-jetpack-compose-ready-for-production-development-28o6</guid>
      <description>&lt;h1&gt;
  
  
  What's this about?
&lt;/h1&gt;

&lt;p&gt;Hi everyone! 👋&lt;/p&gt;

&lt;p&gt;I'm a senior Android engineer who is diving into Jetpack Compose, I'd like to share my experience so this help anyone else going through the same situation. This is probably going to be the first of many posts.&lt;/p&gt;

&lt;p&gt;At this point, everyone knows what Jetpack Compose is about. I’m writing this article to help me figure out if I should start using Jetpack Compose or not – to figure that out I have gone through a little exercise where I have developed a few screens using XML and then see how long it would take me to implement that same UI with Jetpack Compose. Hopefully this article might help you a little in your own journey with (or without) Compose!&lt;/p&gt;

&lt;p&gt;There are &lt;em&gt;many&lt;/em&gt; opinions out there about Jetpack Compose, at both ends of the spectrum, some people love Jetpack Compose unconditionally and some hate it. I try to be neutral and look at both sides equally. I have been doing mobile development for the last 10 years and have grown very cautious about the technologies I pick, my main goal is to use technologies that are comfortable for everyone and not to get the whole team nerd-blocked just because we got caught in the hype of a new technology. &lt;/p&gt;

&lt;p&gt;To make this article a bit more digestible I have recorded a video explaining what I found so far 👇&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/9iBloHXKHbc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  The experiment 🧪
&lt;/h1&gt;

&lt;p&gt;I built a simple app that goes through some of the common scenarios that we see during our day-to-day development.&lt;/p&gt;

&lt;p&gt;These are the XML screens:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FdUvI34---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dsyj5scn44ky9ftmz546.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FdUvI34---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dsyj5scn44ky9ftmz546.png" alt="Image description" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And these are the Compose equivalent screens&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_DjEeKuP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1568ubl0jtt9lgitsf9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_DjEeKuP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1568ubl0jtt9lgitsf9.png" alt="Image description" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we have a simple list that I build with a &lt;code&gt;RecyclerView&lt;/code&gt; + &lt;code&gt;CoordinatorLayout&lt;/code&gt; so I could hide/show that &lt;code&gt;FloatingActionButton&lt;/code&gt;, all this done through XML.&lt;/p&gt;

&lt;p&gt;Second, we have a bit more complex screen, we have a Google Map Fragment wrapped inside a &lt;code&gt;CoordinatorLayout&lt;/code&gt; where we have two &lt;code&gt;FloatingActionButton&lt;/code&gt;, the left &lt;code&gt;FloatingActionButton&lt;/code&gt; triggers a layout using the &lt;code&gt;BottomSheetBehavior&lt;/code&gt; from the &lt;code&gt;CoordinatorLayout&lt;/code&gt; and the right FAB triggers a &lt;code&gt;BottomSheetDialogFragment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, we have a WebView with a loading layout on top.&lt;/p&gt;

&lt;p&gt;You can check the code in this GitHub &lt;a href="https://github.com/4gus71n/XmlToCompose"&gt;repository&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  The results 📈
&lt;/h2&gt;

&lt;p&gt;It took me &lt;strong&gt;three hours&lt;/strong&gt; to implement the XML UI and most of the behaviors, like hiding or showing the &lt;code&gt;FloatingActionButton&lt;/code&gt; when we scroll or the bottom sheet behavior in the Google Maps page, all that was done through XML using a &lt;code&gt;CoordinatorLayout&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning curve 📚
&lt;/h2&gt;

&lt;p&gt;Learning the basics about Jetpack Compose, implementing those same screens, and finally comparing my code to other GitHub repositories took me &lt;strong&gt;three to four days&lt;/strong&gt;. Maybe it is because I already have Android development experience or the &lt;a href="https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect"&gt;Dunning–Kruger&lt;/a&gt; effect (it probably is), but I have to say, learning Jetpack Compose has been really simple so far, if one of the goals for Jetpack Compose is to make UI development easier, they have definitely nailed that.&lt;/p&gt;

&lt;p&gt;The sources I have used so far are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=ERBEWmfz6h0&amp;amp;list=PLSrm9z4zp4mEWwyiuYgVMWcDFdsebhM-r"&gt;Stevdza-San YouTube Channel&lt;/a&gt;: I found this playlist very helpful to learn the basics of JetPack Compose. If you don't know anything at all, this is helpful to introduce you to the basic Compose concepts, like State Hoisting and the basic layouts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.youtube.com/playlist?list=PLQkwcJG4YTCSpJ2NLhDTHhi6XBNfk9WiC"&gt;Philipp Lackner's YouTube Channel&lt;/a&gt;: Great playlist to add on top of the previous one. Also this &lt;a href="https://www.youtube.com/watch?v=A7CGcFjQQtQ"&gt;MVVM tutorial&lt;/a&gt; is great to see all these pieces come toghether.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Foso/Jetpack-Compose-Playground"&gt;Jetpack Compose Playground&lt;/a&gt;: Once you have gone through the basic concepts related to Compose, this GitHub repository has a good amount of content to learn more about each Composable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I had some basic understanding of how Composables work, I compared my solution to some other apps out there — a good thing about Compose is that there is no shortage of Compose clone-apps out there.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vipulasri/JetInstagram"&gt;Compose Instagram Clone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny52525/JetSpotify"&gt;Compose Spotify Clone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cyph3rcod3r/Travel"&gt;Compose Example Travel App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/igorwojda/android-showcase"&gt;Compose Example App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/getStream/whatsApp-clone-compose/"&gt;Compose WhatsApp Clone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ashar-7/JetMessenger"&gt;Compose Messenger Clone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GetStream/stream-slack-clone-android"&gt;Compose Slack Clone&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Things I like so far 👍
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I think it might be easier for new devs to learn Jetpack Compose instead of XML&lt;/strong&gt;: I'm quite comfortable with XML, but that's because I have years of experience behind me, I know all the recipes, tricks and hacks — sure, if you know your way around XML you can probably create features faster than with Jetpack Compose, but not everyone knows all the tricks about &lt;code&gt;CoordinatorLayout&lt;/code&gt; or how to use all the &lt;code&gt;layout_behaviors&lt;/code&gt;, things like that. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It unifies everything&lt;/strong&gt;: I got to learn SwiftUI before Jetpack Compose, and I can see how all these technologies are moving towards declarative UI — iOS with SwiftUI/TCA, Android with Compose/MVVM, Flutter, and React.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I like being able to preview each component individually&lt;/strong&gt;: Sure, you could argue that you can achieve the same thing if you extract your layouts and use the &lt;code&gt;include&lt;/code&gt; tag with the &lt;code&gt;tools&lt;/code&gt; namespace on top of that, but still, I can see Compose UI being less prone to conflicts when submitting code, if you are tidy enough.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RAMwU22X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pcdgk5ftxdvnphw1v6yt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RAMwU22X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pcdgk5ftxdvnphw1v6yt.png" alt="Image description" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I didn't like that much so far 👎
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The preview&lt;/strong&gt;: I think the Composable preview on Android Studio still needs some work. The exercise we have gone through is just a simple single-module app, but if the Android Studio preview needs to build the entire project in order to preview your Composables, this could be an issue on large production-level projects where you have longer building times.  Also, a few things got me thinking; I've seen some people say that they actually create an independent UI module just to use as a playground to build the Composables and then copy-paste them into the main project&lt;a href="https://www.reddit.com/r/androiddev/comments/tqrnfv/is_it_normal_for_jetpack_compose_previews_to_be/"&gt;[1]&lt;/a&gt; — a good workaround, but a workaround nonetheless. Also, I was a bit surprised to see that if you develop desktop UI using IntelliJ + the Jetpack Compose Preview plugin, it actually previews faster than on Android Studio &lt;a href="https://www.reddit.com/r/androiddev/comments/p4gh3q/any_faster_way_to_preview_compose/"&gt;[2]&lt;/a&gt;&lt;a href="https://stackoverflow.com/questions/68623666/jetpack-compose-the-preview-does-not-render-in-real-time#comment121277201_68623666"&gt;[3]&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MsuKxYnk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xuvn6wzj84fi7zacqyuv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MsuKxYnk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xuvn6wzj84fi7zacqyuv.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I miss the &lt;code&gt;CoordinatorLayout&lt;/code&gt;&lt;/strong&gt;: I think the &lt;code&gt;CoordinatorLayout&lt;/code&gt; is one of the, if not the most, powerful layouts. You can achieve common UX behaviors just from the XML. I guess the Compose equivalent would be the Scaffold, but I don't see it is as flexible or as powerful as the &lt;code&gt;CoordinatorLayout&lt;/code&gt; &lt;em&gt;yet&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;MotionLayout&lt;/code&gt; future&lt;/strong&gt;: AFAIK the &lt;code&gt;MotionLayout&lt;/code&gt; will eventually be incorporated into the Compose SDK, but right now is under alpha development &lt;a href="https://www.valueof.io/blog/motionlayout-in-compose-motion-scene-json5"&gt;[1]&lt;/a&gt;&lt;a href="https://blog.canopas.com/explore-compose-motionlayout-773a3462d787"&gt;[2]&lt;/a&gt;. I hope that whatever happens, we get to keep the scene editor, I had to implement some quite tricky animations in the past and the MotionLayout was a lifesaver — being able to preview each frame of the animation, is extremely helpful. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;More @OptIn annotations than I had expected&lt;/strong&gt;: All the examples I have gone through so far use &lt;del&gt;some experimental features&lt;/del&gt; APIs that might go through changes in the future &lt;a href="https://cs.github.com/?scopeName=All+example+GH+repos&amp;amp;scope=repo%3Avipulasri%2FJetInstagram+OR+repo%3Asunny52525%2FJetSpotify+OR++repo%3Acyph3rcod3r%2FTravel+OR+repo%3Aigorwojda%2Fandroid-showcase+OR+repo%3AgetStream%2FwhatsApp-clone-compose+OR+repo%3Aashar-7%2FJetMessenger+OR+repo%3AGetStream%2Fstream-slack-clone-android&amp;amp;q=%40OptIn"&gt;[1]&lt;/a&gt; from either Compose or Material. I'm sure you can use workarounds or alternative solutions to avoid using these APIs, I guess I could have done something else instead of using the &lt;code&gt;BottomSheetScaffold&lt;/code&gt;, but still, I was expecting the API to be more mature at this point. &lt;strong&gt;Edit&lt;/strong&gt;: Apparently, the @OptIn annotation is very commonly used in Compose right now, it means that the API is likely to go through breaking changes in future versions, but it doesn't mean the API is untested or unreliable. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No &lt;code&gt;WebView&lt;/code&gt; support so far&lt;/strong&gt;: The only way to use a &lt;code&gt;WebView&lt;/code&gt; is to bridge the Android SDK &lt;code&gt;WebView&lt;/code&gt; into Composable using the &lt;code&gt;AndroidView&lt;/code&gt; Composable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who else is using Jetpack Compose out there?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Twitter&lt;/strong&gt; has moved some of its UI modules to Jetpack Compose — I highly recommend watching &lt;a href="https://chris.banes.dev/talks/branching-out-to-jetpack-compose/"&gt;Chris Banes' talk during Droidcon 2022&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Airbnb&lt;/strong&gt; did start working with Jetpack Compose during the early preview&lt;a href="https://android-developers.googleblog.com/2022/05/airbnb-uses-jetpack-compose.html"&gt;[1]&lt;/a&gt;. They have also developed a library to easily browse all the Composables within your app&lt;a href="https://medium.com/airbnb-engineering/introducing-showkase-a-library-to-organize-discover-and-visualize-your-jetpack-compose-elements-d5c34ef01095"&gt;[2]&lt;/a&gt; — Vinay Gaba, Airbnb Tech Lead, is one of the biggest Jetpack Compose advocates. I recommend watching his &lt;a href="https://github.com/vinaygaba/Learn-Jetpack-Compose-By-Example"&gt;Jetpack Compose by Example&lt;/a&gt; GitHub repository and going through his &lt;a href="https://www.jetpackcompose.app/"&gt;Compose Snippet library&lt;/a&gt;, which looks like it has potential to become &lt;a href="https://android-arsenal.com/"&gt;Android Arsenal&lt;/a&gt; future successor 😜.&lt;/p&gt;

&lt;p&gt;Some other companies that I'd like to highlight are &lt;a href="https://developer.android.com/jetpack/compose/adopt"&gt;Lyft and Square&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I've also been doing some sleuthing myself — I want to figure out if there are other companies out there using Jetpack Compose. To do that I downloaded the apk binaries from Spotify, TikTok, Uber Eats, WhatsApp, Reddit and decompile them with &lt;a href="https://github.com/skylot/jadx"&gt;jadx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, just to be sure that I could compare correctly, I decompile both the Airbnb app and the Lyft app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--45KTeh2b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n3xylbaexhrp1oimwapd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--45KTeh2b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n3xylbaexhrp1oimwapd.png" alt="Image description" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ETj6PwOP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1b3bple9rpn43am72ph7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ETj6PwOP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1b3bple9rpn43am72ph7.png" alt="Image description" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that both, Lyft and Airbnb, they both have references to the Jetpack Compose packages &lt;code&gt;androidx.compose.*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Spotify, TikTok and WhatsApp did not have any references to Jetpack Compose that I could find 😕&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LQ4wqO6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nduw66j3vopboasrhjsc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LQ4wqO6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nduw66j3vopboasrhjsc.png" alt="Image description" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jMFBd8A5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8l14655gp0uua60e9scv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jMFBd8A5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8l14655gp0uua60e9scv.png" alt="Image description" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M9qvD1-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gx7j6mxktdq8unyzxug2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M9qvD1-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gx7j6mxktdq8unyzxug2.png" alt="Image description" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Uber Eats did have some references to the Jetpack Compose library so it might be possible that they are using it somewhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SSrZrw9y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovi98wwmmwe7qiatp1oy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SSrZrw9y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovi98wwmmwe7qiatp1oy.png" alt="Image description" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, it looks the Reddit app also uses Jetpack Compose — again, seeing these references doesn't really mean much, they might be just experimenting with Jetpack Compose in one place or two.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7QB8VHAI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61mn42u0s8a2u1qa80f8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7QB8VHAI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61mn42u0s8a2u1qa80f8.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I want to try next
&lt;/h2&gt;

&lt;p&gt;I still have a lot to learn. Some of the things I'll be playing with next are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing Composables&lt;/strong&gt;: I'm a big Robolectric fan and I've heard only good things about testing Composables. I'm eager to see how it plays out.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is there any ViewPager2 equivalent?&lt;/strong&gt;: I'm guessing you can implement the equivalent behavior with a &lt;code&gt;LazyRow&lt;/code&gt;, but I haven't tried this yet. I also want to play with the &lt;a href="https://github.com/chrisbanes/accompanist/"&gt;Accompanist's&lt;/a&gt; Pager lib — I understand it was developed as a replacement for the &lt;code&gt;ViewPager&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pagination&lt;/strong&gt;: Pagination with the &lt;code&gt;RecyclerView&lt;/code&gt; is as simple as initializing a custom &lt;code&gt;RecyclerView.OnScollListener&lt;/code&gt; and referencing the &lt;code&gt;LinearLayoutManager&lt;/code&gt; to know when to hit the ViewModel and request the next batch of items — I want to see how complex (or not) it would be to do this with a &lt;code&gt;LazyColumn&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Architect a more scalable solution&lt;/strong&gt;: This experiment has just gone through a little UI and nothing else. I'll architect a serious production-level architecture that allows engineers to create new screens independently with XML or Compose and see what results I get.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Canceling API calls&lt;/strong&gt;: Most Image Loading libraries like Glide or Picasso allow you to cancel the API calls when fetching an image from the network — if you are using a &lt;code&gt;RecyclerView&lt;/code&gt; you can cancel any API call as soon as you dispose the &lt;code&gt;ViewHolder&lt;/code&gt; that way you don't push unnecessary  API calls, I want to see if I can achieve the same with a &lt;code&gt;LazyColumn&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is it necessary to use the Jetpack Compose Navigation library?&lt;/strong&gt; I'm not a big fan of the Navigation library and I'd like to figure out if it is really necessary. Also, what is the expected architecture? One &lt;code&gt;Activity&lt;/code&gt; per screen? One &lt;code&gt;Fragment&lt;/code&gt; per screen? &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Try the Accompanist’s libs&lt;/strong&gt;: Try the &lt;code&gt;SwipeRefreshLayout&lt;/code&gt;, &lt;code&gt;WebView&lt;/code&gt; and &lt;code&gt;ViewPager&lt;/code&gt; replacements from the &lt;a href="https://github.com/chrisbanes/accompanist/"&gt;Accompanist&lt;/a&gt; library. If I understood correctly, this library will be eventually integrated into Jetpack Compose.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review the official docs&lt;/strong&gt;: These API do change and the most up-to-date place are the Android docs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  tl;dr
&lt;/h1&gt;

&lt;p&gt;I think Jetpack Compose is the future of Android development, but I do not think it is wise to rely 100% on it. I would use it on my next project, but I think it is wiser to architect a solution that allows you to use both Compose or XML when you want, so if you run into a situation where a Compose solution isn't doable, you can default to XML.&lt;/p&gt;

&lt;p&gt;I am very cautious when updating my tech stack, I do not like to make life any harder for my co-workers, and I do not like opt-in to new technologies just because they are trendy, that being said — moving from Java to Kotlin was a good thing, moving from MVP to MVVM was a good thing, and moving from Eclipse to IntelliJ was a good thing, now the time to move to Jetpack Compose has come.&lt;/p&gt;

&lt;p&gt;I do not think XML will go extinct anytime soon, not this year or next one or the one after, but in 5 years? Who knows? Is there anyone else out there actually using Java nowadays? — probably, and probably you would not be too comfortable working with that code base. Same thing is going to happen with XML.&lt;/p&gt;

&lt;h1&gt;
  
  
  Feedback! 🙇🙇🙇
&lt;/h1&gt;

&lt;p&gt;Thank you so much for reading this article! Please, let me know what you think and feel free to point out any errors so I can correct them immediately. Also, if there's some topic that you think I should cover next, let me know. Best!&lt;/p&gt;

</description>
      <category>android</category>
      <category>jetpackcompose</category>
      <category>programming</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Software shenanigans</title>
      <dc:creator>Agustín Tomas Larghi</dc:creator>
      <pubDate>Tue, 31 Aug 2021 13:49:31 +0000</pubDate>
      <link>https://dev.to/4gus71n/software-shenanigans-1n94</link>
      <guid>https://dev.to/4gus71n/software-shenanigans-1n94</guid>
      <description>&lt;p&gt;I've decided to start a new series of posts ("Software shenaningans") oriented to soft-skill improvements, communication, how to coordinate things with your team, the kind of things that - opposite to software technologies - won't get outdated.&lt;/p&gt;

&lt;p&gt;Since the pandemic started, I've noticed many companies have turned onto remote work, yet many people are not used to working remotely. So I thought about writing a quick post about a few things I've learned to improve productivity when working remotely.&lt;/p&gt;

&lt;p&gt;FYI, I've been working remotely most of my career, even before COVID, so most of the things I'm going to talk about here come from my remote work experience.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6hfp5jcfexoezkuw9ka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6hfp5jcfexoezkuw9ka.png" alt="Communication Banner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've seen many &lt;em&gt;talented&lt;/em&gt; engineers fail because of communication issues, sometimes because they can't communicate their ideas properly, but more often than not it is because of their own insecurities. I've seen engineers get anxious over having to ping a manager, schedule a meeting, or do an &lt;code&gt;@here&lt;/code&gt; on the Slack general channel. I'd like to think that I'm very loud on Slack, I'm not afraid of hitting that &lt;code&gt;@here&lt;/code&gt; to get people in sync, and &lt;em&gt;no one ever has slapped my wrist by doing so&lt;/em&gt;. No one is ever going to yell at you because you are trying to express your ideas.&lt;/p&gt;

&lt;p&gt;If you are an over-thinker here’s a simple diagram to help you figure things out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flr0j93nveg8wwu8v20mj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flr0j93nveg8wwu8v20mj.png" alt="Overthinker Diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's go through some examples:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx64bokoc7kn1xx5wfius.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx64bokoc7kn1xx5wfius.png" alt="Slack Example 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this okay?&lt;/strong&gt; 🙅‍♂️ No, don't ping the whole channel just to say hello. Is it okay - and I encourage - being cheerful and polite, but don't disrupt people unnecessarily. Thinks that as soon as you do an &lt;code&gt;@here&lt;/code&gt; you will have most people on that channel context switch from whatever they were doing onto Slack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1k58qfql86iyewxzjnt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1k58qfql86iyewxzjnt.png" alt="Slack Example 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this okay?&lt;/strong&gt; 🙅‍♂️ No, if things are getting on 🔥 ping everyone, the faster you all address the issue, the better. Again, think that most people when they look at Slack they will see something like this 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhu0kc820uh509fbcj8lo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhu0kc820uh509fbcj8lo.png" alt="Slack Example 2 Channel Highlight"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All channels highlighted all the time, unless you ping people directly, chances are they won't notice what you just write until the end of the day 😕.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76p8pqt5cavm7jp5jgss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76p8pqt5cavm7jp5jgss.png" alt="Slack Example 3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this okay?&lt;/strong&gt; 👍 Yes, the same thing we were talking about before, if you need someone to take immediate action on something that is blocking you, ping that person directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Yet More Slack tips
&lt;/h3&gt;

&lt;p&gt;1️⃣ Keep 🧵 conversations&lt;/p&gt;

&lt;p&gt;If you don't keep the conversation flow tidy on Slack, things can get messy. For example, picture you jump on the &lt;code&gt;#engineering&lt;/code&gt; channel, and you see you are 30-something messages behind this convo 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnp055bkv02pt1369qs8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnp055bkv02pt1369qs8.png" alt="More Slack Tips 30 Something Convo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not easy to catch up, right? For new people joining this conversation, it may be hard to get the context of what's going on when you don't know where the conversation &lt;em&gt;started&lt;/em&gt;. And it will very likely take you a considerable amount of time until you do catch up. Plus, things can get muddy if other people are talking about different things.&lt;/p&gt;

&lt;p&gt;Keeping &lt;strong&gt;threaded&lt;/strong&gt; conversations may mitigate this issue 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o9uhwzj8v7eyict99ey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o9uhwzj8v7eyict99ey.png" alt="Keep Threaded Convos"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, you can later link this conversation to &lt;code&gt;Jira&lt;/code&gt;/&lt;code&gt;Azure&lt;/code&gt;/&lt;code&gt;Trello&lt;/code&gt; tickets - or whatever CMS your team uses - for future reference. Same for the PRs, you can link the Slack conversations so whoever reviews your PR can have a little bit of context about what the changes are for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnw6mj5auxblirahpi9em.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnw6mj5auxblirahpi9em.png" alt="PR Tempalte"&gt;&lt;/a&gt;&lt;br&gt;
I'm a big fan of using PR templates, and linking the Slack convos to the PRs gives much more context to the person doing the review.&lt;/p&gt;

&lt;p&gt;2️⃣ Using the &lt;code&gt;/remind&lt;/code&gt; command&lt;/p&gt;

&lt;p&gt;Usually, on my day-to-day, I do almost all my work following the reminders of the day before. The &lt;code&gt;/remind&lt;/code&gt; me command is quite helpful to keep track of things.&lt;/p&gt;

&lt;p&gt;Let's say you were discussing something with someone and you need to follow up later 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fae0rrrqherm9pdro9u8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fae0rrrqherm9pdro9u8f.png" alt="RemindMe Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can target the &lt;code&gt;/remind&lt;/code&gt; command to different people or channels, not just to yourself. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1hmfzjhv2i6rvhd9pms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1hmfzjhv2i6rvhd9pms.png" alt="RemindMe Example 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For me this is &lt;em&gt;the&lt;/em&gt; most powerful tool on Slack.&lt;/p&gt;

&lt;p&gt;3️⃣ Grouping channels into &lt;em&gt;sections&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As we were saying before, most of us, when we open Slack in the morning, the first thing we see is just a humongous list of highlighted channels. Some of them we don't even care - for example, a &lt;code&gt;#random&lt;/code&gt; channel just for fun, channels that are completely unrelated to the work we do, etc.&lt;br&gt;
So instead of having to waste time going through each channel, sort out the channels by &lt;em&gt;priority&lt;/em&gt; using sections.&lt;/p&gt;

&lt;p&gt;Something like this 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe81njem3m1qdyzligs26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe81njem3m1qdyzligs26.png" alt="Sections Before"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Becomes this 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgri3v7xkq3yp34q3mfn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgri3v7xkq3yp34q3mfn9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That way you can check the truly important channels first, and if you have time, go through the rest later.&lt;/p&gt;

&lt;p&gt;4️⃣ Rule of thumb&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid DMs as much as possible&lt;/strong&gt;. Try to post on public channels as often as possible, in my experience, teams who communicate through public channels perform better than the ones who rely mostly on DMs. Think that if you are discussing something with someone else via a private DM 🅰️ other people won't be able to catch up with what you are talking about, you may be talking about some technical adjustments in the back-end with just one single engineer, what if some other engineer had something to say about but he couldn't because he didn't know about this conversation? 🅱️ Also, you cannot link or reference private DMs, if someone new joins the DM he won't be able to see any previous conversation.&lt;/p&gt;

&lt;p&gt;Finally, speaking on public channels encourages people to speak up, be more transparent, and it removes that feeling that some people have of--"Oh, I better not talk about this &lt;em&gt;here&lt;/em&gt; so I don't open a can of worms!"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always try to ping people directly&lt;/strong&gt;. It is unlikely that people will reply immediately to you if you don't ping them directly 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1e85xdzs7txa0sr9a9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1e85xdzs7txa0sr9a9a.png" alt="Ping Directly Example"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkl2at0bbht73cps6t57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkl2at0bbht73cps6t57.png" alt="Email Etiquette"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've seen so many people so quick to jump the 🔫 and hit the SEND button way too soon when sending emails. A bad email can cause more confusion than anything else, and if the &lt;em&gt;tone&lt;/em&gt; set in the email sounds &lt;em&gt;rude&lt;/em&gt; it can complicate things even further. So, I'll just drop some tips I've found useful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read your emails aloud before you send them.&lt;/strong&gt; The &lt;em&gt;tone&lt;/em&gt; in an email is key. I remember once I had this lengthy email thread going on with a back-end engineer from a contractor company working with us. We would &lt;em&gt;only&lt;/em&gt; talk via email, so that made things even more difficult. And these guys were working in a completely opposite time zone than ours, so it was basically sending an email, and waiting for the response to come back the next day. I'm not going to lie, I was getting annoyed by the whole situation 😅.&lt;br&gt;
The whole thread was about us asking for some credentials to use an SDK in the front-end, and we were going back and forth with the back-end guys because they would provide the credentials but this wouldn't work as expected. So at some point, I replied something like this 👇&lt;/p&gt;

&lt;p&gt;&lt;em&gt;John, I know this is doable because I've seen it in the documentation here: &lt;a href="http://somelink-to-some-documentation.com" rel="noopener noreferrer"&gt;http://somelink-to-some-documentation.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Of course, one minute after hitting send, I'm pinged via Slack by one of my Engineer Managers - and rightly so - and he asks me to read my response aloud, and then I realize that I'd completely dropped the ball. The tone was &lt;em&gt;rude&lt;/em&gt;, and it didn't get us any further into solving the issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Double check names&lt;/strong&gt;. Many people, myself included, can get annoyed when other people misspell their names. Just take a minute to double-check that you have correctly written the name of the person you are addressing. Once, in one of my previous jobs, I was helping a new hire, a new Android engineer getting on board. We would talk over email because he didn't have his Slack credentials set yet. I usually go that extra mile when onboarding someone because I want them to have a pleasant experience. But this person would keep misspelling my name on every email. Augustin instead of &lt;em&gt;Agustin&lt;/em&gt;. So, I let the first email pass. Then in the second email, I bold my signature so he could see my name. The same thing again, so I finally add a PS on the third email -- "FYI my name spells Agustin 🙂". So, take that extra minute to double-check names. Don't skip accents on the letters. Otherwise, it looks as if you don't care about the email content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are not a native speaker, use some tools to enhance your grammar.&lt;/strong&gt; A text with spelling mistakes won't be taken as seriously as one that's not. There are some great tools out there to avoid common grammar mistakes. Like &lt;a href="https://www.grammarly.com" rel="noopener noreferrer"&gt;Grammarly&lt;/a&gt; or &lt;a href="http://gingersoftware.com/" rel="noopener noreferrer"&gt;Ginger&lt;/a&gt;. Both are great tools to prevent those little details, that especially us who are not native English speakers, often do.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi8ed5e1omri8ajfc5yxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi8ed5e1omri8ajfc5yxg.png" alt="Introduction Banner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A good intro is a nice way to start a new job with the right foot. I always encourage people to present themselves when they are about to join a new team, just doing a quick intro on the Slack &lt;code&gt;#general&lt;/code&gt; 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F631emsyoz59kyt0j0aev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F631emsyoz59kyt0j0aev.png" alt="Slack Intro"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would even recommend that you ask your manager if it would be okay to send an email to your whole &lt;em&gt;team&lt;/em&gt; presenting yourself more in detail 👇&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fic8tovfhi7o66gcivkbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fic8tovfhi7o66gcivkbq.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you know, try to be &lt;em&gt;personal&lt;/em&gt;, don't be like -- "Hi, I'm Agus and I like coding, and coding is also my hobby". I don't think anyone has so little going on in their lives. Also, sometimes using emojis helps to set the &lt;em&gt;emotional tone&lt;/em&gt; in a message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzdunsgt6bdghtnepa4j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzdunsgt6bdghtnepa4j.png" alt="Meeting Etiquette"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1️⃣ &lt;strong&gt;Always have your camera turned on&lt;/strong&gt;. Seeing someone else's face on the screen helps people connect more, and it also helps you being taken more seriously by your teammates. I mean, if someone has their camera turned off, you don't know what they are doing, right? Are they paying attention to what you're saying? Are they watching Netflix? &lt;br&gt;
If you feel uncomfortable by having 50 people stare at you through your webcam during a meeting, &lt;strong&gt;just switch tabs&lt;/strong&gt;. I often do that if I have to talk at a meeting where I know there's going to be a lot of people, I just switch tabs so I don't have to see the amount of users or anything else, but I keep my cam turned on so everyone else can see me.&lt;br&gt;
It is okay if you don't want to turn on your webcam during a meeting because you're having lunch - although I don't recommend having lunch in front of your laptop, it's not healthy 🙂 - or because of something else. But do so once &lt;em&gt;you've become comfortable with your team&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;2️⃣ &lt;strong&gt;If you are in a noisy environment use audio filtering.&lt;/strong&gt;You can buy a headset with noise filtering included or use audio filtering software such as &lt;a href="https://krisp.ai" rel="noopener noreferrer"&gt;Krisp&lt;/a&gt;. Most people won't even tell you if you have noise in the background out of shyness or because they think it would be disrespectful. So, make sure no one has to try to figure out what you are saying behind the barking of your 🐕. &lt;/p&gt;

&lt;p&gt;3️⃣ &lt;strong&gt;If you need to say something, but feel uncomfortable interrupting people, use the chat section&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhx0579dxrmbc8jvtnvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhx0579dxrmbc8jvtnvc.png" alt="Zoom Chat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4️⃣ &lt;strong&gt;Watch out for the "radio silence" during conversations.&lt;/strong&gt; When you start working with a new team where each member knows little to nothing about each other, or if you start working with a team that isn't used to work remotely, you may run into those uncomfortable situations where someone asks a question, a minute goes by, and no one answers. Then, &lt;em&gt;what can we do to solve these situations?&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;For example, let's say we are on a Zoom call, and one of the front-end engineers asks a question like:-&lt;em&gt;" How is the UX supposed to look like when we navigate into this section, and there's no internet connectivity?"&lt;/em&gt; Let's say 10 seconds go by then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;1️⃣ If you think you know to answer the question, answer it. It doesn't matter if you think it would be a dumb answer, it will prompt someone else to chime in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2️⃣ Redirect the question to someone who may know how to answer the question. For example, if a front-end engineer wants to know how to implement the UX for a specific edge-case scenario, redirect that question to one of the designers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3️⃣ If no one knows how to answer the question, they are taking too much time, or just going back and forth, then park the convo and propose to follow up on a Slack thread later.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Something else to keep in mind is that these situations may be a direct result of a lack of &lt;em&gt;ownership&lt;/em&gt; across team members. It takes time and effort to boost people into taking more ownership, this is something that the team achieves through regular 1:1 meetings and giving honest feedback to each other.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxt5otl6aiuc9yrslvt2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxt5otl6aiuc9yrslvt2.png" alt="TLDR Banner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reach out to people. Don't overthink about sending messages or scheduling a meeting with your teammates. That's what we are paid to do.&lt;/li&gt;
&lt;li&gt;Don't be an emotionless drone. Talk to people over Slack as if you were in a real office.&lt;/li&gt;
&lt;li&gt;Step up, have ownership of the product. Even if sometimes you think it is not your responsibility to lead some initiative or organize something, just do it. It will make everyone’s work - including yours - easier in the future.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>A production-level architecture for Android apps (part 2)</title>
      <dc:creator>Agustín Tomas Larghi</dc:creator>
      <pubDate>Sat, 15 Feb 2020 22:07:36 +0000</pubDate>
      <link>https://dev.to/4gus71n/a-production-level-architecture-for-android-apps-part-2-2j5l</link>
      <guid>https://dev.to/4gus71n/a-production-level-architecture-for-android-apps-part-2-2j5l</guid>
      <description>&lt;h1&gt;
  
  
  Previous
&lt;/h1&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/4gus71n" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oUNRqOqP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/practicaldev/image/fetch/s--zNYB1EFo--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/332452/f0798b13-d1a0-4951-af83-91d4202278d8.png" alt="4gus71n"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/4gus71n/a-production-level-architecture-for-android-apps-part-1-271m" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;A production-level architecture for Android apps (part 1)&lt;/h2&gt;
      &lt;h3&gt;Agustín Tomas Larghi ・ Feb 15 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#android&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#architecture&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#kotlin&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#mvvm&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  Index
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Context&lt;/li&gt;
&lt;li&gt;Layers&lt;/li&gt;
&lt;li&gt;DataSources&lt;/li&gt;
&lt;li&gt;Repositories&lt;/li&gt;
&lt;li&gt;Interactors&lt;/li&gt;
&lt;li&gt;ViewModel&lt;/li&gt;
&lt;li&gt;View&lt;/li&gt;
&lt;li&gt;Notes&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Context
&lt;/h1&gt;

&lt;p&gt;We are going to go through all the different &lt;strong&gt;layers&lt;/strong&gt; of the architecture, explaining what’s the &lt;strong&gt;protocol&lt;/strong&gt; and the &lt;strong&gt;logic&lt;/strong&gt; of each layer. The protocol is what the layer exposes to the above layer. The logic of the layer is what the layer is supposed to do internally.&lt;/p&gt;

&lt;h1&gt;
  
  
  Layers
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;“This architecture looks like an overkill, you can achieve the same thing using less layers, and it will scale properly.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It may, it may not. I’m used to working on large-scale projects, where the requirements may change from one day to the next, where you may have to tweak features all the time. That’s why I try to go with the most flexible architecture possible. This is what I’ve been using and it has worked fine for me. I prefer to waste 5 minutes creating some extra classes today, that spend 1 day trying to wire something up, so it just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Datasources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Protocol
&lt;/h3&gt;

&lt;p&gt;The protocol of the data sources is pretty straightforward, &lt;strong&gt;fetch data&lt;/strong&gt; from somewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logic
&lt;/h3&gt;

&lt;p&gt;There are only two places from where we can fetch information, from the &lt;strong&gt;network&lt;/strong&gt; (through API endpoints or some other service) or from the &lt;strong&gt;cache&lt;/strong&gt; (either through something like the &lt;code&gt;SharedPreferences&lt;/code&gt;, a database, or some other local source)&lt;/p&gt;

&lt;h4&gt;
  
  
  Network Logic
&lt;/h4&gt;

&lt;p&gt;Fetching information from a &lt;strong&gt;backend&lt;/strong&gt; API through RESTful endpoints. For this example, I’m using Retrofit. There are some other tools out there like Volley, but discussing the pros and cons of each tool goes beyond the scope of this article.&lt;/p&gt;

&lt;h4&gt;
  
  
  Code example
&lt;/h4&gt;

&lt;p&gt;With Retrofit, you usually just declare an &lt;strong&gt;API Interface&lt;/strong&gt; that represents the available endpoints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Retrofit's interface that we use to communicate with a mock API hosted on apiary.io
 */&lt;/span&gt;
&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ExampleApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Fetches a paginated response containing the news.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"news"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiNewFeedListResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches the most recently viewed news.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"news/recent"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchRecentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiNewFeedListResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches the user profile information
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchProfileInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;   &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiProfileResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches the user business skills
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"business"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchBusinessSkills&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiBusinessSkilssResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches a list of cooking recipes
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"recipes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchAllRecipes&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiRecipeResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ExampleAPI&lt;/code&gt; interface to fetch information from a mock API hosted on &lt;a href="https://apiary.io"&gt;ApiAry&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Retrofit's interface API that we use to communicate with the Imgur API.
 */&lt;/span&gt;
&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ImgurApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches the comments associated with one particular post.
     * @param id The id of the post.
     * @param sort "best" if we want to fetch the comments sorted by the most up voted. "new" if
     * we want to fetch the comments sorted by date.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gallery/{id}/comments/{sort}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="c1"&gt;// best, new&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiCommentsResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches the posts using the specified search criteria.
     * @param sort "top" If we want to fetch the latest posts. "viral" If we want to fetch the
     * most trendy posts.
     * @param window If sort equals "viral" this parameter gets ignored. If sort equals "top" we
     * can specify if we want to fetch the most important posts from this "month", "day", or "week"
     * @param query The search query.
     * @param page The page number, starts from 1.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gallery/search/{sort}/{window}/{page}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;searchGallery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"top"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// viral, top&lt;/span&gt;
        &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"window"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// if sort == top { day, week, month }&lt;/span&gt;
        &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"q"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiImgurGalleryResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches the posts using a tag.
     * @param sort "top" If we want to fetch the latest posts. "viral" If we want to fetch the
     * most trendy posts.
     * @param perPage Max amount of results per page.
     * @param tag The tag query.
     * @param page The page number, starts from 1.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gallery/t/{tag}/{sort}/{page}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;searchTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"top"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// viral, top&lt;/span&gt;
        &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"perPage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;perPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiImgurTagResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ImgurAPI&lt;/code&gt; interface that we use to fetch image posts from through the &lt;a href="https://apidocs.imgur.com/?version=latest"&gt;Imgur API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Notice that on each API call we return a &lt;code&gt;rx.Single&lt;/code&gt;, that’s because when we hit an API endpoint we send a request and get a &lt;em&gt;single&lt;/em&gt; response.&lt;/p&gt;

&lt;p&gt;The second step is to expose the Retrofit dependency to all our other dependencies, we do this through the network modules (&lt;a href="https://github.com/4gus71n/Examples/blob/master/core/src/main/java/com/kimboo/core/di/modules/ExampleNetworkModule.kt"&gt;ExampleNetworkModule&lt;/a&gt; and &lt;a href="https://github.com/4gus71n/Examples/blob/master/core/src/main/java/com/kimboo/core/di/modules/ImgurNetworkModule.kt"&gt;ImgurNetworkModule&lt;/a&gt;) and the &lt;a href="https://github.com/4gus71n/Examples/blob/master/core/src/main/java/com/kimboo/core/di/modules/RetrofitModule.kt"&gt;RetrofitModule&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the network modules we build the REST client and we configure all the things that we need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Module&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleNetworkModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;provideGson&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Gson&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;GsonBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFieldNamingPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FieldNamingPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LOWER_CASE_WITH_UNDERSCORES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// Gson deserializer to deserialize dates from the Imgur API&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTypeAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DateDeserializer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;provideOkHttpClient&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;provideRetrofit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;okHttpClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gson&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Gson&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://private-cc89a2-examples21.apiary-mock.com/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConverterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GsonConverterFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gson&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCallAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RxJava2CallAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;okHttpClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ExampleNetworkModule&lt;/code&gt; where we provide all the dependencies that we need to fetch data from the ApiAry backend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Module&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImgurNetworkModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="nd"&gt;@Named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authInterceptor"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;providesAuthInterceptor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Interceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Interceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Client-ID 6ea78556ea84b48"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="nd"&gt;@Named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"imgurOkHttp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;provideOkHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authInterceptor"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;authInterceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Interceptor&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authInterceptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="nd"&gt;@Named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"imgurRetrofit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;provideRetrofit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"imgurOkHttp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;imgurOkHttp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;gson&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Gson&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.imgur.com/3/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConverterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GsonConverterFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gson&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCallAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RxJava2CallAdapterFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgurOkHttp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;strong&gt;Gson&lt;/strong&gt; to deserialize the endpoint’s responses and turn them into &lt;strong&gt;backend model classes&lt;/strong&gt; (naming convention  &lt;em&gt;ApiSomethingResponse&lt;/em&gt;). We provide the &lt;strong&gt;OkHttp&lt;/strong&gt; client that the Retrofit instance is going to use as their HTTP client. And finally, we provide the actual &lt;strong&gt;Retrofit&lt;/strong&gt; instance that we are going to use to instantiate the API interface.&lt;/p&gt;

&lt;p&gt;Notice that in the &lt;a href="https://github.com/4gus71n/Examples/blob/master/core/src/main/java/com/kimboo/core/di/modules/ImgurNetworkModule.kt"&gt;ImgurNetworkModule&lt;/a&gt; we need to provide an OkHttp instance that authenticates against the Imgur API. We need to provide different Retrofit instances on both modules, since one points to the Imgur base host and the other to the ApiAry host. Also, notice that we actually reuse the same Gson instance for both modules.&lt;/p&gt;

&lt;p&gt;Then we have the &lt;strong&gt;RetrofitModule&lt;/strong&gt; where we provide the API Interface instance for both the Imgur API and the ApiAry API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Module&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RetrofitModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;provideExampleApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;retrofit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ExampleApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExampleApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;provideImgurApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"imgurRetrofit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;imgurRetrofit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ImgurApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;imgurRetrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ImgurApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cache Logic
&lt;/h4&gt;

&lt;p&gt;Fetching information &lt;strong&gt;locally&lt;/strong&gt;, either through the &lt;code&gt;SharedPreferences&lt;/code&gt;, a DB or some other source. For this example, I’m using &lt;strong&gt;Room&lt;/strong&gt;. There are some other tools that you can use like Realm or SQLDelight.&lt;/p&gt;

&lt;h4&gt;
  
  
  Code example
&lt;/h4&gt;

&lt;p&gt;There are a few classes that we need to implement when setting up Room. The first is the &lt;strong&gt;RoomDatabase&lt;/strong&gt; class, that basically handles the DB creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;entities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;DbRecipeDto&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;exportSchema&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@TypeConverters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Converters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleRoomDatabase&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RoomDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;useInMemory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ExampleRoomDatabase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;databaseBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;useInMemory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inMemoryDatabaseBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ExampleRoomDatabase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;databaseBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ExampleRoomDatabase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"example.db"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;databaseBuilder&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allowMainThreadQueries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fallbackToDestructiveMigration&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;recipesDao&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;RecipesDao&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to create the classes to map the &lt;strong&gt;database responses&lt;/strong&gt; (naming convention &lt;em&gt;DbSomethingDto&lt;/em&gt;) into objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"db_recipe_dto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;unique&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;DbRecipeDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@PrimaryKey&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isBookmarked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we are mapping a table that stores information about cooking recipes.&lt;/p&gt;

&lt;p&gt;Next, we have to create the DAO to query the information from the database (naming convention &lt;em&gt;SomethingDao&lt;/em&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Dao&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RecipesDao&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT * FROM db_recipe_dto"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getAllRecipes&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Flowable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DbRecipeDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT * FROM db_recipe_dto where id = :id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getRecipeById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Flowable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DbRecipeDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nd"&gt;@Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onConflict&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OnConflictStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IGNORE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;storeRecipes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;dbRecipeDtos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DbRecipeDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LongArray&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nd"&gt;@Update&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;updateRecipes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbRecipeDto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DbRecipeDto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that if we want to keep track of the database changes we use &lt;code&gt;Flowables&lt;/code&gt; to stream the query’s results. If we don’t care or if we want to listen for the results just once, we can use &lt;code&gt;rx.Single&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repositories
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Protocol
&lt;/h3&gt;

&lt;p&gt;The repository protocol is to &lt;strong&gt;expose data&lt;/strong&gt; from the &lt;strong&gt;data sources&lt;/strong&gt; through a &lt;strong&gt;unified reactive interface&lt;/strong&gt; (&lt;code&gt;rx.Obsevable&lt;/code&gt;). This data may come from the &lt;strong&gt;network layer&lt;/strong&gt;, because we hit some endpoint, or may come from the &lt;strong&gt;cache layer&lt;/strong&gt; because we ran some query on a database or fetched something from the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logic
&lt;/h3&gt;

&lt;p&gt;It fetches the data from the data source and wraps the response with a &lt;code&gt;DataResponse&lt;/code&gt;¹ class. This DataResponse class is used to carry around any information that we may need about the origin of this data (if it’s a cached response, if it came from the backend through a 304, or if it came from the backend through a 200, etc.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Code example
&lt;/h3&gt;

&lt;p&gt;Let’s say that in our example app we need a repository to access all the information related to the &lt;strong&gt;news&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Keeping in mind that we have these two endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;    &lt;span class="cm"&gt;/**
     * Fetches a paginated response containing the news.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"news"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiNewFeedListResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches the most recently viewed news.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"news/recent"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchRecentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiNewFeedListResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the interface (&lt;em&gt;protocol&lt;/em&gt;) of our repository would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;NewsRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchRecentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiNewFeedListResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiNewFeedListResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the only thing that these repositories are supposed to expose are &lt;code&gt;rx.Observables&lt;/code&gt;, doing that we are able to plug different repositories that fetch information from different sources through RxJava.&lt;/p&gt;

&lt;p&gt;The actual implementation (&lt;em&gt;logic&lt;/em&gt;) of our repository would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewsNetworkRepository&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retrofitApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ExampleApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;uiScheduler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Scheduler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;backgroundScheduler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Scheduler&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NewsRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchRecentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiNewFeedListResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;retrofitApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchRecentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observeOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uiScheduler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribeOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backgroundScheduler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DataResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiNewFeedListResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;retrofitApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observeOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uiScheduler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribeOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backgroundScheduler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;toDataResponse()&lt;/code&gt; function, is just an extended function from &lt;code&gt;rx.Observable&lt;/code&gt; that wraps the stream on a &lt;code&gt;DataResponse&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why making the Repositories return rx.Observables?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You need your repositories to have a sort of &lt;em&gt;plug-and-play&lt;/em&gt; behavior. That means, using a reactive API (&lt;code&gt;zip&lt;/code&gt;, &lt;code&gt;flatMap&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;, &lt;code&gt;concat&lt;/code&gt;, etc.) so you are able to &lt;strong&gt;combine&lt;/strong&gt; different &lt;strong&gt;repository calls&lt;/strong&gt; within your Interactors.&lt;/p&gt;

&lt;p&gt;For example, if you need to create an Interactor that’s going to return both, the recently viewed news and all the news, you can &lt;em&gt;zip&lt;/em&gt; the &lt;code&gt;fetchRecentNews()&lt;/code&gt; and the &lt;code&gt;fetchNews(page)&lt;/code&gt; calls together.&lt;/p&gt;

&lt;p&gt;Another example, if you need to hit one endpoint and based on the response from that endpoint, hit another endpoint, you can just &lt;em&gt;flat map&lt;/em&gt; the calls from both repositories and that’s it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That’s wrong, if you have to zip two API calls together, you should get the backend to return both responses together through one single endpoint.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Again, these are &lt;strong&gt;real-world&lt;/strong&gt; scenarios, and unless you’re working for a company that solely relies on mobile as its source of income (Uber, Instagram, WhatsApp, etc.) mobile is probably going to be a &lt;em&gt;second-class&lt;/em&gt; citizen. Meaning that you may not get all the endpoints that you need. That’s neither good nor bad, it is just how it works. But if you have to juggle API calls together, better to keep it tidy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why using a response wrapper?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve found that using a wrapper class to &lt;strong&gt;keep track&lt;/strong&gt; of the &lt;strong&gt;origin&lt;/strong&gt; of the data is very useful. That &lt;code&gt;DataResponse&lt;/code&gt; wrapper that we have there is just a simple class that keeps track of where the data is coming from.&lt;/p&gt;

&lt;p&gt;For example, let’s say that you want to do one thing if a specific endpoint returns a 401 (Not Authorized) and another if the endpoint returns a 500 (Internal Server Error). You can do that just by looking at the &lt;code&gt;DataResponse&lt;/code&gt; wrapper.&lt;/p&gt;

&lt;p&gt;Another example, let’s say that you’re storing the information on a cache layer if the endpoint replies with a 304 (Not Modified) you want to skip that from being stored on cache (because it’s the exact same information that you already have, so it’s going to be a waste of time storing that information gain). So using a wrapper class to keep track of the source of the information may come handy when trying to handle some specific scenarios.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why do you separate your repositories into interface and implementation?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's something that I got used to from working with &lt;strong&gt;clean architectures&lt;/strong&gt;. I can't say that it's super useful all the time, but I do like the idea of having a contract between a component's protocol and its implementation. Even if requires some extra work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Protocol
&lt;/h3&gt;

&lt;p&gt;The interactor protocol is to &lt;strong&gt;expose data&lt;/strong&gt; to the &lt;strong&gt;ViewModels&lt;/strong&gt; through a &lt;strong&gt;callback interface&lt;/strong&gt;. The data that we expose from the interactor to the ViewModel goes as &lt;strong&gt;model classes&lt;/strong&gt;. While on the repository layer we may return backend model classes (ApiSomethingResponse) from the network API or database responses (DbSomethingDTO) from the database, the interactor returns plain model classes. By doing this we completely &lt;strong&gt;decouple&lt;/strong&gt; the backend responses, the database responses and the actual model classes that we use across the app.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That looks like an insane amount of work, keeping three different model classes for the backend responses, database responses and for using on the app. Why not just use the same class to map the backend responses (through Gson) and keeping the database tables?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Like many other topics that we talked about in this article, most of the layers are totally customizable. If you think that it is better to use one single model class for mapping the backend responses, the database tables, and for use across the app, you can do it. &lt;strong&gt;My personal experience tells me otherwise&lt;/strong&gt;. Things may change on the backend, new fields may get exposed, some fields may get removed, you may need to track specific fields within the database, etc. I prefer to code a bit more of boilerplate code and be ready for changes that stick with a short solution and risk reliability.&lt;/p&gt;

&lt;h4&gt;
  
  
  Logic
&lt;/h4&gt;

&lt;p&gt;An interactor uses one or more &lt;strong&gt;repositories&lt;/strong&gt; to perform an action, this may be something like calling two different endpoints, wait for them to reply, and then return that information through the callback interface.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why using a callback interface here? Shouldn’t we expose a rx.Observables?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My personal experience is that it is better to hide everything related to RxJava beneath the interactor layer. If you are working on a large-scale project you may have some junior developers on the team, and working with technologies such as RxJava it’s a bit tricky for inexperienced developers. I find it more comfortable to keep all this hidden beneath the interactor layer, that way the most senior developers can word developing the interactors, and then the developers with less experience can just use them on the ViewModels.&lt;/p&gt;

&lt;p&gt;Also, it saves you a bit of boilerplate code. Let’s say that you need to zip two repositories together to fetch data and show it on an Activity. If have to do the same thing again on some other Activity you would have to duplicate the code for zipping those repositories.&lt;/p&gt;

&lt;p&gt;And finally, I think it looks tidier to have some sort of contract (callback interface) between the use cases and the ViewModels.&lt;/p&gt;

&lt;h4&gt;
  
  
  Code example
&lt;/h4&gt;

&lt;p&gt;This is how the interface (&lt;em&gt;protocol&lt;/em&gt;) of the interactor looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Fetches the recently viewed news and the paginated news altogether.
 */&lt;/span&gt;
&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;GetNewsInteractor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Callback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/**
         * Triggered when we successfully fetch the paginated news
         * @param news The list of news for this page.
         * @param currentPage The current page.
         * @param totalPages The total amount of pages available.
         */&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onNewsSuccessfullyFetched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;news&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewFeed&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
            &lt;span class="n"&gt;currentPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;totalPages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="cm"&gt;/**
         * Triggered when we successfully fetch the recently viewed news.
         * @param news The list of recently viewed news.
         */&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onRecentlyViewedNewsFetched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;news&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewFeed&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="cm"&gt;/**
         * Triggered when something goes wrong either fetching the recently viewed news or
         * the paginated news.
         */&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onErrorFetchingNews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="cm"&gt;/**
         * Triggered when we try to hit the endpoints without internet connection.
         */&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onNoInternetConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Execute function to trigger this Interactor.
     * @param callback A GetNewsInteractor.Callback usually implemented on the ViewModel.
     * @param page The current page that we are trying to fetch.
     */&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we see the &lt;code&gt;GetNewsInteractor&lt;/code&gt;, an interactor that fetches a list of &lt;strong&gt;recently viewed news&lt;/strong&gt; and &lt;strong&gt;paginated news&lt;/strong&gt; through two different endpoints.&lt;/p&gt;

&lt;p&gt;Here I'm just handling two error scenarios for when the API call fails and for when we try to hit the API without an internet connection. If you need to handle more in-detail errors, for example, a 401/403 error response from the API, you could just add a new &lt;code&gt;onNotAuthorizedError()&lt;/code&gt; callback function here and implement the handling of that exception in the actual implementation.&lt;/p&gt;

&lt;p&gt;The implementation (&lt;em&gt;logic&lt;/em&gt;) looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetNewsInteractorImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;newsRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NewsRepository&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GetNewsInteractor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GetNewsInteractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// If we are fetching the first page either because we did a PTR or because this is the&lt;/span&gt;
        &lt;span class="c1"&gt;// first time opening this screen we need to fetch both&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;fetchRecentlyViewedNewsAndPagedNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pagedNews&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;
                    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;recentlyViewedNews&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;second&lt;/span&gt;

                    &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNewsSuccessfullyFetched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;news&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagedNews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;currentPage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagedNews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;totalPages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagedNews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onRecentlyViewedNewsFetched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;news&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ConnectException&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;UnknownHostException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNoInternetConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onErrorFetchingNews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;fetchPagedNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNewsSuccessfullyFetched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;news&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;currentPage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;totalPages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ConnectException&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;UnknownHostException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNoInternetConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onErrorFetchingNews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches both, the recently viewed news from the /news/recent and the paginated news from the
     * /news?page={page}
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchRecentlyViewedNewsAndPagedNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pair&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewFeedMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;NewFeedMetadata&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Observables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetchPagedNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;fetchRecentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;pagedNews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NewFeedMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NewFeedMetadata&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="nc"&gt;Pair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pagedNews&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches the recently viewed news from the /news/recent endpoint.
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchRecentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewFeedMetadata&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchRecentlyViewedNews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toNewsFeedMetadata&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetches the paginated news through the /news?page={page}
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchPagedNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NewFeedMetadata&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchNews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toNewsFeedMetadata&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that we call the &lt;code&gt;fetchRecentlyViewedNewsAndPagedNews(Int)&lt;/code&gt; function which zips two calls from the &lt;code&gt;NewsRepostitory&lt;/code&gt; and returns them wrapped into a &lt;code&gt;Pair&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Through RxJava you can manage to handle the responses however you want. For example, let’s say that, specifically, if the call to fetch the recently viewed orders fail you want to display a &lt;code&gt;Snackbar&lt;/code&gt; on the UI, you can hook from the &lt;code&gt;doOnError()&lt;/code&gt; and use the callback to communicate the ViewModel that something went wrong while trying to fetch the recently viewed news.&lt;/p&gt;

&lt;p&gt;Also, you can pass any other dependencies that you might need. Let’s say that you want to track these errors through Crashlitycs, you can pass the Crashlitycs instance into the interactor and report the exceptions hooking from either the &lt;code&gt;doOnError()&lt;/code&gt; or in the &lt;code&gt;onError()&lt;/code&gt; from the &lt;code&gt;Subscriber&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same thing goes if the behavior of your interactor depends on some &lt;code&gt;SharedPreferences&lt;/code&gt; configurations. You just pass the &lt;code&gt;SharedPreferences&lt;/code&gt; instance through the interactor’s constructor and check whatever value you need and call one function from the repositories or the other.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What about memory leaks? Don’t you dispose the Observable once you’re done with it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, disposing of the &lt;code&gt;Observables&lt;/code&gt; as soon as you are done with them is a good practice. You can hook from the &lt;code&gt;onCleared()&lt;/code&gt; function on the ViewModel and dispose of all the &lt;code&gt;Observables&lt;/code&gt; through the interactor. For the sake of keeping things simple, I didn’t include an example of that on every interactor. But you can see an example of that in the &lt;code&gt;GetImgurPostsInteractor&lt;/code&gt; and how we call the &lt;code&gt;dispose()&lt;/code&gt; function from within the &lt;code&gt;ExampleImgurViewModel&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ViewModel
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Protocol
&lt;/h3&gt;

&lt;p&gt;The ViewModel protocol is to &lt;strong&gt;expose data&lt;/strong&gt; to the &lt;strong&gt;view layer&lt;/strong&gt; through &lt;strong&gt;LiveData&lt;/strong&gt; properties. These LiveData properties are usually MutableLiveData properties of &lt;strong&gt;sealed classes&lt;/strong&gt; that represent the &lt;strong&gt;state&lt;/strong&gt; of the ViewModel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logic
&lt;/h3&gt;

&lt;p&gt;There are a few little rules from the ADT about what we are supposed to do on a ViewModel and what not. One of them is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not use or handle any view-related classes on the ViewModel. The ViewModel is not supposed to know anything about the view layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then there are a few rules that I came out with by myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can extend from the &lt;code&gt;AndroidViewModel&lt;/code&gt; class and use the Application context, in case that for example, you need to fetch a String resource. I &lt;strong&gt;highly discourage&lt;/strong&gt; using the &lt;code&gt;AndroidViewModel&lt;/code&gt; class, because I think that anything related to fetching resources and things like this should be done on the view layer. Also, if you need to use an Android SDK class that isn't view-related, for example as the SharedPreferences, you should use them inside your interactors. There's no need to have this logic inside the ViewModel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Try to keep as few &lt;code&gt;LiveData&lt;/code&gt; properties as possible. This makes the ViewModel easier to handle and easier to scale. Ideally, you should have a single &lt;code&gt;MutableLiveData&lt;/code&gt; property that represents the &lt;strong&gt;state&lt;/strong&gt; of the ViewModel and that’s it. By state I mean, “showing the information”, “loading”, “no internet connection”.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sometimes you may need to have more than one &lt;code&gt;LiveData&lt;/code&gt; property or even use a &lt;code&gt;MediatorLiveData&lt;/code&gt; property to handle changes within the other properties. For example, let’s say that you have an Activity where you can search for different things using a search bar on the Toolbar. Usually, you will have a &lt;code&gt;MutableLiveData&lt;/code&gt; property of a String to represent the query of what you are searching for, you can hook this into a &lt;code&gt;MediatorLiveData&lt;/code&gt; and listen for the changes on the query &lt;code&gt;LiveData&lt;/code&gt; property, then perform the search accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another example is trying to &lt;em&gt;handle different error situations&lt;/em&gt;. Let’s say that you have an endpoint that is prone to return a 500 response. If the user opens the app from scratch and gets a 500 when fetching the data you want to show a full display error message (a nice ImageView with a nice title explaining what just happened). On the other hand, if the user opens the app, fetches information successfully, then do a Pull-To-Refresh, and gets a 500, you may want to show just a &lt;code&gt;Snackbar&lt;/code&gt; instead of the full error display. In a situation like this, you want to keep two different sealed classes to represent the state of the app. One that represents the state of the ViewModel, and the other that represents all the possible error messages. You can see an example of this in the &lt;strong&gt;"LiveData Example"&lt;/strong&gt;, there we display a list of posts through a search feature, this search feature also goes with a few filtering options, all this gets coordinated through &lt;code&gt;LiveData&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Code Example
&lt;/h4&gt;

&lt;p&gt;Taking a look to the &lt;strong&gt;"LiveData Example"&lt;/strong&gt; we are going to see that we have two sealed classes to represent the state of the ViewModel, &lt;code&gt;State&lt;/code&gt; and &lt;code&gt;Message&lt;/code&gt;. And another sealed class to represent the state of the current search, &lt;code&gt;SearchParams&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;State&lt;/code&gt; class which can be either &lt;code&gt;IsLoading&lt;/code&gt; (to reflect whether we are currently loading the search results or not), &lt;code&gt;Success&lt;/code&gt; (to reflect the success state once we have fetched the search results), &lt;code&gt;NoInternetError&lt;/code&gt; (to reflect the state when there's no internet connection) and &lt;code&gt;UnknownError&lt;/code&gt; (to reflect the state when something goes wrong while trying to fetch results)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurViewModel&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;getImgurPostsInteractor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GetImgurPostsInteractor&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;GetImgurPostsInteractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Callback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// region Sealed classes declaration&lt;/span&gt;
    &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;IsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ImgurGallery&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;NoInternetError&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;UnknownError&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Message&lt;/code&gt; class represents the &lt;strong&gt;error messages&lt;/strong&gt;. As I mentioned before, if we got search results through a search and then we try to search for something else and something unexpected happen (a 500 response from the endpoint, connectivity lost, etc.) we don't want to remove the information that the user is currently seeing and show the full error state, we just want to display a nice friendly &lt;code&gt;Snackbar&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;NoInternetError&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;UnknownError&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// endregion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that on the &lt;code&gt;GetImgurPostsInteractor.Callback&lt;/code&gt; implementation within the &lt;code&gt;ExampleImgurViewModel&lt;/code&gt; we handle two error callbacks, one for generic errors and the other for connectivity errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onErrorFecthingImgurPosts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_imgurGalleryPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UnknownError&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UnknownError&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onNoInternetConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_imgurGalleryPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NoInternetError&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NoInternetError&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing that we do is to turn off the loading state through the &lt;code&gt;State.IsLoading(false)&lt;/code&gt;. Then we check against the &lt;code&gt;_imgurGalleryPosts&lt;/code&gt; property which is a mutable collection of the Imgur posts that we have fetched so far. If the collection is empty, then we know that the user isn't seeing any post-related information, so we go with the full error state. If the collection isn't empty, then we know that the user is seeing posts, and we don't want to hide them, so we go with the message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;SimpleSearchView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnQueryTextListener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;Toolbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnMenuItemClickListener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PopupMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnMenuItemClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="c1"&gt;// region Lifecycle functions declaration&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="nf"&gt;observeStateChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;observeMessageChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;observeMessageChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Observer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NoInternetError&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;onShowNoInternetConnectionSnackbar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UnknownError&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;onShowUnknownErrorSnackbar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onShowUnknownErrorSnackbar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Snackbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;activityExampleImgurContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_unknown_error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Snackbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LENGTH_LONG&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onShowNoInternetConnectionSnackbar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Snackbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;activityExampleImgurContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_no_internet_connection&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Snackbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LENGTH_LONG&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;observeStateChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Observer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsLoading&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;onLoadingStateChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NoInternetError&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;onShowNoInternetConnectionError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UnknownError&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;onShownUnknownErrorStateView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;onShowNoResultsStateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;onShowImgurPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onShownUnknownErrorStateView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;activityExampleImgurStateDisplay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ic_sentiment_very_dissatisfied_black_24dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_unknown_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onShowNoResultsStateView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;activityExampleImgurStateDisplay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ic_youtube_searched_for_black_24dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_no_search_result_found&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onShowNoInternetConnectionError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;activityExampleImgurStateDisplay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ic_signal_wifi_off_black_24dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_no_internet_connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you can see how we observe these changes from the view layer and react accordingly. Keep in mind that in the source code we have a custom view called &lt;code&gt;SimpleStateDisplay&lt;/code&gt;, which is basically just a &lt;code&gt;FrameLayout&lt;/code&gt; with an &lt;code&gt;ImageView&lt;/code&gt; and a &lt;code&gt;TextView&lt;/code&gt; that I use to display the full error states.&lt;/p&gt;

&lt;p&gt;To perform the actual search we have a &lt;code&gt;MediatorLiveData&lt;/code&gt; property of &lt;code&gt;SearchParams.Query&lt;/code&gt; called &lt;code&gt;searchParams&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurViewModel&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;getImgurPostsInteractor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GetImgurPostsInteractor&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;GetImgurPostsInteractor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Callback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;TOP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"top"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;VIRAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"viral"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"day"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;WEEK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"week"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;MONTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"month"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableLiveData&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SearchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SearchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TOP&lt;/span&gt; &lt;span class="c1"&gt;// Default sort&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;window&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableLiveData&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SearchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SearchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MONTH&lt;/span&gt; &lt;span class="c1"&gt;// Default window&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableLiveData&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="c1"&gt;// Default query&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableLiveData&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;// Default page&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

    &lt;span class="c1"&gt;// LiveData property that reflects the state of what we are currently searching.&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediatorLiveData&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SearchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;newSort&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="c1"&gt;// Remove all the current posts since the query has changed&lt;/span&gt;
            &lt;span class="n"&gt;_imgurGalleryPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;sort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newSort&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;newWindow&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="c1"&gt;// Remove all the current posts since the query has changed&lt;/span&gt;
            &lt;span class="n"&gt;_imgurGalleryPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newWindow&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;newQuery&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="c1"&gt;// Remove all the current posts since the query has changed&lt;/span&gt;
            &lt;span class="n"&gt;_imgurGalleryPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newQuery&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;newPage&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newPage&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea here is that from the View layer we set all the different properties to configure our search criteria. We set the query, the page, the sorting order, the window, etc. The &lt;code&gt;searchParams&lt;/code&gt; reacts to these changes and updates its state. The &lt;code&gt;_searchParams&lt;/code&gt; property is a &lt;code&gt;SearchParam.Query&lt;/code&gt; property that helps us to keep track of the current search criteria, internally, in the ViewModel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;SimpleSearchView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnQueryTextListener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;Toolbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnMenuItemClickListener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PopupMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnMenuItemClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;observeQueryChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Observer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We observe the &lt;code&gt;searchParams&lt;/code&gt; changes from the View layer and if we detect that anything has changed on the search criteria, we call the ViewModel and perform a new search against the Imgur API through the &lt;code&gt;GetImgurPostsInteractor&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  View
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Protocol
&lt;/h3&gt;

&lt;p&gt;The view protocol is to &lt;strong&gt;expose data&lt;/strong&gt; to the &lt;strong&gt;user&lt;/strong&gt; through a UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logic
&lt;/h3&gt;

&lt;p&gt;The logic on the view layer consist of interacting with the ViewModel, observe the changes on the ViewModel through the LiveData properties and update the UI accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;

&lt;p&gt;Here you just do the usual set up of the views. For example, setting the listener for a &lt;code&gt;Button&lt;/code&gt;, preparing the adapters for a &lt;code&gt;RecyclerView&lt;/code&gt;, or setting up any other view. Other than that the implementation of the view layer should be pretty straightforward. In the &lt;code&gt;onCreate(Bundle?)&lt;/code&gt; hook we want to inject the dependencies, instantiate the &lt;code&gt;ViewModel&lt;/code&gt;, set up the views, and listen for the &lt;code&gt;ViewModel&lt;/code&gt; changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleImgurActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;SimpleSearchView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnQueryTextListener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;Toolbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnMenuItemClickListener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PopupMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnMenuItemClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

    &lt;span class="c1"&gt;// region Lifecycle functions declaration&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;setContentView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example_imgur_activity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Instantiate the ViewModel&lt;/span&gt;
        &lt;span class="n"&gt;viewInjector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;viewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ViewModelProviders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;viewModelProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExampleImgurViewModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Setup the Views' listeners&lt;/span&gt;
        &lt;span class="nf"&gt;setupSwipeRefreshLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;setupRecyclerView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;setupToolbar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;// Observe the changes from the ViewModel&lt;/span&gt;
        &lt;span class="nf"&gt;observeStateChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;observeMessageChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;observeQueryChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;observeQuerySortChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;observeQueryWindowChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;observeSearchQueryChanges&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another thing that you need to care for is the state of the Fragment/Activity. You can save and restore the state of the view through the &lt;code&gt;onSavedInstanceState(Bundle)&lt;/code&gt; and &lt;code&gt;onRestoreInstanceState(Bundle)&lt;/code&gt; hooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onSaveInstanceState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onSaveInstanceState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onSaveIntanceState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onRestoreInstanceState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onRestoreInstanceState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onRestoreInstanceState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;What about using Jetpack's SavedStateHandle?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the time of writing this articles implementing this solution using &lt;code&gt;SavedStateHandle&lt;/code&gt; requires way more boilerplate code than just hooking the ViewModel from the  &lt;code&gt;onSavedInstanceState(Bundle)&lt;/code&gt; and &lt;code&gt;onRestoreInstanceState(Bundle)&lt;/code&gt; hooks. I'll probably update the article as soon as I find a more adequate solution.&lt;/p&gt;

&lt;h1&gt;
  
  
  Next
&lt;/h1&gt;

&lt;p&gt;My next article will be about how to integrate Unit Testing (through Robolectric + Square's MockWebServer) and Instrumentation Testing (through Espresso) into this architecture.&lt;/p&gt;

&lt;h1&gt;
  
  
  Notes
&lt;/h1&gt;

&lt;p&gt;[1] The DataResponse "pattern" is also used by Google on the &lt;a href="https://github.com/google/iosched/blob/89df01ebc19d9a46495baac4690c2ebfa74946dc/shared/src/main/java/com/google/samples/apps/iosched/shared/result/Result.kt"&gt;2019 IOSched app&lt;/a&gt;. It is really useful when you need to do something based on the origin of the data.&lt;/p&gt;

</description>
      <category>android</category>
      <category>mvvm</category>
      <category>kotlin</category>
      <category>architecture</category>
    </item>
    <item>
      <title>A production-level architecture for Android apps (part 1)</title>
      <dc:creator>Agustín Tomas Larghi</dc:creator>
      <pubDate>Sat, 15 Feb 2020 17:39:46 +0000</pubDate>
      <link>https://dev.to/4gus71n/a-production-level-architecture-for-android-apps-part-1-271m</link>
      <guid>https://dev.to/4gus71n/a-production-level-architecture-for-android-apps-part-1-271m</guid>
      <description>&lt;h1&gt;
  
  
  Next
&lt;/h1&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/4gus71n" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F332452%2Ff0798b13-d1a0-4951-af83-91d4202278d8.png" alt="4gus71n"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/4gus71n/a-production-level-architecture-for-android-apps-part-2-2j5l" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;A production-level architecture for Android apps (part 2)&lt;/h2&gt;
      &lt;h3&gt;Agustín Tomas Larghi ・ Feb 15 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#android&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#mvvm&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#kotlin&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#architecture&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  Context
&lt;/h1&gt;

&lt;p&gt;I thought it was time to write a &lt;strong&gt;series of articles&lt;/strong&gt; about how to design a proper, &lt;strong&gt;scalable&lt;/strong&gt; architecture that you may apply to any &lt;strong&gt;medium-large projects&lt;/strong&gt;. This series of articles is the sum of all the knowledge that I’ve learned from working on different projects for different companies. Working on all these different projects has taught me the &lt;strong&gt;does and donts&lt;/strong&gt; that you need to keep in mind when you are working on a large project. There are things related to the &lt;strong&gt;technology itself&lt;/strong&gt; and there are things related to how the architecture that you choose to apply is going to impact your &lt;strong&gt;engineering team&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;p&gt;This article assumes that you are at a &lt;strong&gt;semi-senior&lt;/strong&gt; or &lt;strong&gt;senior&lt;/strong&gt; level. That means that you feel comfortable working with the most well-known technologies related to Android development, such as Dagger, Kotlin, RxJava, Google-related libraries (FCM, Remote Config, Crashlytics) and obviously, the Android SDK.&lt;/p&gt;

&lt;p&gt;I’m going to start talking about the &lt;strong&gt;tech-stack setup&lt;/strong&gt;, then show the &lt;strong&gt;example app&lt;/strong&gt; that I have created for this article, and then in the next article I'll go on through each &lt;strong&gt;layer&lt;/strong&gt; of the architecture.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tech-stack setup
&lt;/h1&gt;

&lt;p&gt;Since some time ago I have permanently moved from &lt;strong&gt;MVP&lt;/strong&gt; to &lt;strong&gt;MVVM&lt;/strong&gt; because MVVM has become the first &lt;strong&gt;officially supported&lt;/strong&gt; Android architecture, through Android’s LiveData and ViewModels. Also, since this article aims towards a &lt;strong&gt;large-scale&lt;/strong&gt; architecture this is going to be a &lt;strong&gt;multi-module&lt;/strong&gt; architecture, with &lt;strong&gt;feature modules&lt;/strong&gt;, a &lt;strong&gt;core module&lt;/strong&gt;, and a &lt;strong&gt;base module&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The technologies that I’m going to use here are; &lt;strong&gt;Dagger&lt;/strong&gt; for dependency injection, &lt;strong&gt;OkHttp/Retrofit&lt;/strong&gt; as my networking layer, &lt;strong&gt;RxJava&lt;/strong&gt; to wrap the network layer into repositories.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example App
&lt;/h1&gt;

&lt;p&gt;I have created an &lt;a href="https://github.com/4gus71n/Examples" rel="noopener noreferrer"&gt;example app&lt;/a&gt; as an example - no pun intended - of how this architecture works. This example app shows different real world scenarios, scenarios where we have to use cache, perform search, mix different endpoints, and so on.&lt;/p&gt;

&lt;p&gt;Each &lt;strong&gt;feature module&lt;/strong&gt; shows different use cases and different scenarios. I often see that when you look for examples online you only see the most straightforward, simple and unrealistic scenarios¹. For example, just fetching something from the backend and display it on the UI. In my personal experience that’s not how real-world development works like. In the real world, you’re going to have to go back and forth with different resources within your company, and due to different constraints you may have to settle with API endpoints that are not totally mobile-friedly², or you may have to hit different endpoints to display the whole information to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  RxJava Example #1
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FBtOVU4d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FBtOVU4d.jpg" alt="Imgur"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "RxJava Example #1" shows how we can conditionally zip two API calls together.&lt;/p&gt;

&lt;p&gt;In this example, we use two different endpoints to fetch the "recently viewed news" and the regular paginated news. If we do a Pull-to-Refresh or open the Activity from scratch, we are going to fetch both, the recently viewed news and the first page of the paginated news. If we continue to scroll down until we reach the next page, we are only going to call the API to fetch the second page.&lt;/p&gt;

&lt;p&gt;This example also handles error situations like doing a Pull-to-Refresh without internet connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  RxJava Example #2
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FZ6pmlpM.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FZ6pmlpM.jpg" alt="Imgur"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "RxJava Example #2" shows how we can zip responses from different endpoints through RxJava.&lt;/p&gt;

&lt;p&gt;In this example, we zip the responses from two different endpoints, one that provides the user's "business skills information" and another that provides the user's "personal information". We zip these requests together through RxJava and use wrapper objects to carry the responses around. Also, we handle different error situations for each endpoint and update the UI accordingly.&lt;/p&gt;

&lt;p&gt;This example supports Pull-to-Refresh behavior and handling no internet connection states.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache Example
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FmrSnMuk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FmrSnMuk.jpg" alt="Imgur"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "Cache Example" shows how we can use a &lt;strong&gt;cache repository&lt;/strong&gt; and a &lt;strong&gt;network repository&lt;/strong&gt; to display data to the user, update data dynamically, and without using any sort of EventBus technology, get these updates into the UI by relying on Room's Flowables.&lt;/p&gt;

&lt;p&gt;In this example, we fetch a list of recipes from a mock endpoint host on ApiAry, store those recipes on cache, and allow the user to bookmark the recipes. When we bookmark a recipe we can see the change reflected on the recipe list as soon as we do it, all this thanks to Room's Flowables.&lt;/p&gt;

&lt;p&gt;This example also supports Pull-to-Refresh behavior and handles different error states, such as trying to fetch the list of recipes without internet connectivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  LiveData Example
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FGS6rwc4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FGS6rwc4.jpg" alt="Imgur"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "LiveData Example" shows how we can achieve a complex search by relying on LiveData.&lt;/p&gt;

&lt;p&gt;In this example, we can search image posts using the &lt;strong&gt;Imgur API&lt;/strong&gt;, through this API we are able to search using a &lt;strong&gt;query&lt;/strong&gt; String as the search term, and we have a few filtering options. We can select if we want to get the &lt;strong&gt;latest&lt;/strong&gt; posts (Top) or if we want to get the &lt;strong&gt;most trendy&lt;/strong&gt; posts (Viral). If we choose to filter by top posts, we can also specify a date windows, getting the top posts from the &lt;strong&gt;day&lt;/strong&gt;, &lt;strong&gt;week&lt;/strong&gt;, or month.&lt;/p&gt;

&lt;p&gt;This example supports handling the Pull-To-Refresh gesture and pagination. Also, we handle different error states such as trying to search for something without internet connection (it shows a Snackbar if we are seeing content or a full error state if we open the Activity from scratch).&lt;/p&gt;

&lt;p&gt;All this is done through LiveData.&lt;/p&gt;

&lt;h1&gt;
  
  
  Next
&lt;/h1&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/4gus71n/a-production-level-architecture-for-android-apps-part-2-2j5l"&gt;next article&lt;/a&gt;, I will go in detail through each layer of the architecture.&lt;/p&gt;

&lt;h1&gt;
  
  
  Notes
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;[1]&lt;/strong&gt; By unrealistic scenarios I mean when you go through an article and they show you how to fetch data, save it on cache, and display it on a RecyclerView. That’s all well and good; But what happens if you fetch a 500 (Internal error)? What happens if you fetch a 304 (Not Modified)? Do you waste time and store a duplicate response on the cache? What happens if there’s no internet connection? All these edge-case scenarios, that are actually very likely to happen in the real world, are totally omitted by most articles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[2]&lt;/strong&gt; Some may argue that if this was to happen, you should push forward and settle for nothing else other than what you want. But that’s not how a company works. A company needs to deliver a product. Backend and frontend developers, they have deadlines, and trying to fix an endpoint so it is more mobile-friendly may take time, time that the backend resources don’t have.&lt;/p&gt;

</description>
      <category>android</category>
      <category>architecture</category>
      <category>kotlin</category>
      <category>mvvm</category>
    </item>
  </channel>
</rss>
