<?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>Forem: Michael Mavris</title>
    <description>The latest articles on Forem by Michael Mavris (@mavris).</description>
    <link>https://forem.com/mavris</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%2F3200%2F3520168.jpeg</url>
      <title>Forem: Michael Mavris</title>
      <link>https://forem.com/mavris</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mavris"/>
    <language>en</language>
    <item>
      <title>How to synchronize versions and build numbers across different targets</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Sun, 27 Sep 2020 18:21:32 +0000</pubDate>
      <link>https://forem.com/mavris/how-to-synchronize-versions-and-build-numbers-across-different-targets-51pp</link>
      <guid>https://forem.com/mavris/how-to-synchronize-versions-and-build-numbers-across-different-targets-51pp</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DzLbiVTe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1601205866516-f42ce3289f98%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DzLbiVTe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1601205866516-f42ce3289f98%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" alt="How to synchronize versions and build numbers across different targets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you ever developed an extension for iOS, you are most likely to be familiar with the following errors when uploading the app in the App Store:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CFBundleVersion Mismatch&lt;/strong&gt; - The CFBundleVersion value '1' of extension 'ExtensionApp' does not match the CFBundleVersion value '2' of its containing iOS application 'Main.app'.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CFBundleShortVersionString Mismatch&lt;/strong&gt; - The CFBundleShortVersionString value '1' of extension 'ExtensionApp' does not match the CFBundleShortVersionString value '2' of its containing iOS application 'Main.app'.&lt;/p&gt;

&lt;p&gt;If you have only one extension it's not that difficult to keep them synchronized, but things get tricky when you add more than one extension. In my case, I have a project that has 6 targets and I need to keep the synchronized.&lt;/p&gt;

&lt;p&gt;Since we are programmers, and our job is to solve problems, I thought that it must be a better way to synchronize the target's versions! The first thing that came to my mind was to use &lt;code&gt;Run Script Phase&lt;/code&gt; but then I thought it will get messy for 6 different targets.&lt;/p&gt;

&lt;p&gt;After digging around, I found a much better and much cleaner solution. We will use the &lt;strong&gt;User-Defined&lt;/strong&gt; setting that they will be used by all of the targets.  All we have to do is go to the project's &lt;code&gt;Build Settings&lt;/code&gt;, click on the plus sign and add two settings and call them &lt;code&gt;APP_VERSION&lt;/code&gt; and &lt;code&gt;APP_BUILD&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gWRRo1sC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/09/Screenshot-2020-09-27-at-6.45.07-PM-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gWRRo1sC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/09/Screenshot-2020-09-27-at-6.45.07-PM-1.png" alt="How to synchronize versions and build numbers across different targets"&gt;&lt;/a&gt;Adding the User-Defined Settings&lt;/p&gt;

&lt;p&gt;You will find the newly added settings at the bottom of the &lt;code&gt;Build Settings&lt;/code&gt; tab. Set the &lt;code&gt;APP_VERSION&lt;/code&gt; to match the main target's version (eg. &lt;code&gt;3.4.1&lt;/code&gt;)  and the &lt;code&gt;APP_BUILD&lt;/code&gt; to match its build number (eg.&lt;code&gt;15&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;These settings are accessible throughout the project using the &lt;code&gt;$(APP_VERSION)&lt;/code&gt; and &lt;code&gt;$(APP_BUILD)&lt;/code&gt; variables. Now, we need to go through each target's &lt;code&gt;Info.plist&lt;/code&gt; file and set the &lt;code&gt;Build Version&lt;/code&gt; value to &lt;code&gt;$(APP_BUILD)&lt;/code&gt; and the &lt;code&gt;Build Version String (short)&lt;/code&gt; to &lt;code&gt;$(APP_VERSION)&lt;/code&gt;. From now on, you should be changing the version and the build number from the &lt;code&gt;Build Settings&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fpbN4PSz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/09/Screenshot-2020-09-27-at-6.51.10-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fpbN4PSz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/09/Screenshot-2020-09-27-at-6.51.10-PM.png" alt="How to synchronize versions and build numbers across different targets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it!&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;a href="//mailto:hello@rockandnull.com"&gt;Say hello&lt;/a&gt; or &lt;a href="https://twitter.com/rockandnull"&gt;tweet us&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>ios</category>
      <category>xcode</category>
      <category>swift</category>
      <category>objectivec</category>
    </item>
    <item>
      <title>How to create a white label iOS app (Part 5)</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Fri, 11 Sep 2020 19:23:29 +0000</pubDate>
      <link>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-5-45oc</link>
      <guid>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-5-45oc</guid>
      <description>&lt;p&gt;On &lt;a href="https://dev.to/mavris/how-to-create-a-white-label-ios-app-part-4-44gf"&gt;Part4&lt;/a&gt; we discussed how to use &lt;code&gt;.plist&lt;/code&gt; files with different values for each target. The &lt;code&gt;.plist&lt;/code&gt; approach will help us build the brand for each target, avoiding the boilerplate code.&lt;/p&gt;

&lt;p&gt;We couldn't build the brand for each target without discussing the target's app icons. There are two ways to use a different set of App icons and we are going to discuss them both.&lt;/p&gt;

&lt;p&gt;You can download the project &lt;a href="https://github.com/Rock-and-Null/iOS---WhiteLabel/releases/tag/Part4"&gt;here&lt;/a&gt; as we left it on &lt;a href="https://github.com/Rock-and-Null/iOS---WhiteLabel/releases/tag/Part5"&gt;Part4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first and easiest way is to use the &lt;code&gt;Assets.xcassets&lt;/code&gt; for every target. Just go to plus sign and select &lt;strong&gt;App Icons &amp;amp; Launch Images -&amp;gt; New iOS App Icon&lt;/strong&gt; and name it &lt;code&gt;TargetName-AppIcon&lt;/code&gt;. Then, at the target's   &lt;strong&gt;General&lt;/strong&gt; tab and select the &lt;code&gt;TargetName-AppIcon&lt;/code&gt; in &lt;strong&gt;AppIcon Source.&lt;/strong&gt; That's it!&lt;/p&gt;

&lt;p&gt;The second way is to create a different &lt;code&gt;.xcassets&lt;/code&gt; for each target, that will contain only the &lt;code&gt;AppIcon&lt;/code&gt; of the specific target. For our project, we are going to create a new asset catalog called &lt;code&gt;TestCoffee.xcassets&lt;/code&gt; (under TestCoffee resource folder) with target membership only for TestCoffee target and then we are going to set it as &lt;strong&gt;AppIcon Source&lt;/strong&gt; in its &lt;strong&gt;General&lt;/strong&gt; tab. Now, we have to create an AppIcon called &lt;code&gt;AppIcon&lt;/code&gt;.  The tricky part now, is to set the appropriate AppIcon for each target. For some reason, Xcode won't let you set it directly but I found a workaround. You need to select "Don't use assets catalog", then select any other file in Xcode and then go back to the target's &lt;strong&gt;General&lt;/strong&gt; tab. You will notice that a new button appeared called "Use assets catalog". Tap on that and select the appropriate asset, then select "Migrate". Now you can select the AppIcon from the specific Asset Catalog. Repeat the same procedure for every target.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n5U4IJlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/09/Screenshot-2020-09-11-at-10.11.25-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n5U4IJlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/09/Screenshot-2020-09-11-at-10.11.25-PM.png" alt="How to create a white label iOS app (Part 5)"&gt;&lt;/a&gt;The structure for each target&lt;/p&gt;

&lt;p&gt;I personally prefer the second way because is a cleaner structure especially if you have a big number of targets.&lt;/p&gt;

&lt;p&gt;That's the end of the "White label" series! I hope you found it helpful! If you have any other part that you want me to cover or you want to discuss, please don't hesitate to drop me an &lt;a href="//mailto:hello@rockandnull.com"&gt;email&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find the final project &lt;a href="https://github.com/Rock-and-Null/iOS---WhiteLabel/releases/tag/Part6"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;a href="//mailto:hello@rockandnull.com"&gt;Say hello&lt;/a&gt; or &lt;a href="https://twitter.com/rockandnull"&gt;tweet us&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app/"&gt;How to create a white label iOS app (Part 1)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-2/"&gt;How to create a white label iOS app (Part 2)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-3/"&gt;How to create a white label iOS app (Part 3)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-4/"&gt;How to create a white label iOS app (Part 4)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-5/"&gt;How to create a white label iOS app (Part 5)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>xcode</category>
    </item>
    <item>
      <title>How to create a white label iOS app (Part 4)</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Fri, 04 Sep 2020 06:56:07 +0000</pubDate>
      <link>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-4-44gf</link>
      <guid>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-4-44gf</guid>
      <description>&lt;p&gt;On &lt;a href="https://dev.to/mavris/how-to-create-a-white-label-ios-app-part-3-14bm"&gt;Part3&lt;/a&gt; we discussed how to use Preprocessor Flags to change the result of the &lt;code&gt;coffeeDescription&lt;/code&gt; function based on which target was executing the function. As I noted, I don't like the Preprocessor Flags solution but it was good example to understand how they work.&lt;/p&gt;

&lt;p&gt;You can download the project &lt;a href="https://github.com/Rock-and-Null/iOS---WhiteLabel/releases/tag/Part4" rel="noopener noreferrer"&gt;here&lt;/a&gt; as we left it on &lt;a href="https://dev.to/mavris/how-to-create-a-white-label-ios-app-part-3-14bm"&gt;Part3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A much better alternative is to use a &lt;code&gt;.plist&lt;/code&gt; file (Properties List) for each target. We are going to create a &lt;code&gt;.plist&lt;/code&gt; file for each target and we are going to load these properties in the app.&lt;/p&gt;

&lt;p&gt;We will create 2 &lt;code&gt;.plist&lt;/code&gt; files for our targets, and the name of each file it will be the Bundle ID. The &lt;strong&gt;&lt;code&gt;rockandnull.com.TestCoffee.plist&lt;/code&gt;&lt;/strong&gt; will be visible only to &lt;code&gt;TestCoffee&lt;/code&gt; target and the &lt;strong&gt;&lt;code&gt;rockandnull.com.RealCoffee.plist&lt;/code&gt;&lt;/strong&gt; will be visible only to &lt;code&gt;RealCoffee&lt;/code&gt; target (using target memberships).&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%2Fwww.rockandnull.com%2Fcontent%2Fimages%2F2020%2F09%2FScreenshot-2020-09-04-at-12.57.12-AM.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%2Fwww.rockandnull.com%2Fcontent%2Fimages%2F2020%2F09%2FScreenshot-2020-09-04-at-12.57.12-AM.png" alt="How to create a white label iOS app (Part 4)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The objective is to load the properties in the target using a singleton class called &lt;strong&gt;&lt;code&gt;PropertiesController&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Foundation

class PropertiesController {

    static let sharedInstance = PropertiesController()
    var description : String!

    func loadProperties() {
        let bundleID = Bundle.main.bundleIdentifier
        if let path = Bundle.main.path(forResource: bundleID, ofType: "plist") {
            //CAUTION: We are not using best practices for simplicity. Using plain string as a key and force unwrapping is prone to errors.
            if let nsDictionary = NSDictionary(contentsOfFile: path) {
                self.description = nsDictionary["description"] as? String ?? ""
            }
        }
    }
}

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

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;&lt;code&gt;loadProperties&lt;/code&gt;&lt;/strong&gt;  function is responsible to load the specific &lt;code&gt;.plist&lt;/code&gt; file using the Bundle ID (that's why we named our .plist files after the Bundle ID). Then it converts the &lt;code&gt;.plist&lt;/code&gt; file to an &lt;code&gt;NSDictionary&lt;/code&gt; and then assigns the value of each key to the specific properties.&lt;/p&gt;

&lt;p&gt;We need to load the properties first thing, so we are going to add this code in &lt;strong&gt;&lt;code&gt;AppDelegate&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&amp;gt; Bool {
        // Override point for customization after application launch.
        PropertiesController.sharedInstance.loadProperties()
        return true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we will modify our &lt;code&gt;coffeeDescription&lt;/code&gt; function in &lt;strong&gt;&lt;code&gt;Coffee&lt;/code&gt;&lt;/strong&gt; class&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; static func coffeeDescription() -&amp;gt; String {
      return PropertiesController.sharedInstance.description
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last thing (I promise) to complete this approach is to add a property (String) in &lt;strong&gt;&lt;code&gt;rockandnull.com.TestCoffee.plist&lt;/code&gt;&lt;/strong&gt; with the key &lt;code&gt;description&lt;/code&gt; and value &lt;code&gt;TestCoffee&lt;/code&gt;. Repeat the procedure for &lt;code&gt;RealCoffee&lt;/code&gt; and set the value as &lt;code&gt;RealCoffee&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we will run the app and we will notice that we have accomplished the same result with the Preprocessor Flags solution.&lt;/p&gt;

&lt;p&gt;So, why the fuss? It's a much cleaner solution. In &lt;a href="https://dev.to/mavris/how-to-create-a-white-label-ios-app-part-3-14bm"&gt;Part3&lt;/a&gt; we had only 2 targets and we used 7 lines of code. Imagine how much boilerplate code we would end up having in a project with 30 targets. With the &lt;code&gt;.plist&lt;/code&gt; solution, our function's size will remain the same no matter how many targets we have added.&lt;/p&gt;

&lt;p&gt;Also, you can add other types of data in the &lt;code&gt;.plist&lt;/code&gt;, like a number, a boolean, a date, etc. I use the &lt;code&gt;.plist&lt;/code&gt; approach to store the API endpoint, a copy that is different for each brand and specific color as HEX. This approach will work nicely if you discuss it with your designer and ask him/her to group the project colors in let's say, 6 groups. Then you will create 6 properties and you will assign the HEX code for each color and load them in &lt;code&gt;PropertiesController&lt;/code&gt; as we did with description. Now, for each view that you want to set a color you will use the &lt;code&gt;PropertiesController.sharedInstance.Colour1&lt;/code&gt;. That's it you can now change the colors of the whole app from the &lt;code&gt;.plist&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use with caution!&lt;/strong&gt; Don't use the &lt;code&gt;.plist&lt;/code&gt; approach to store sensitive data. A &lt;code&gt;.plist&lt;/code&gt; file is unencrypted and can easily be fetched from a device. I can't stress this enough!&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;a href="//mailto:hello@rockandnull.com"&gt;Say hello&lt;/a&gt; or &lt;a href="https://twitter.com/rockandnull" rel="noopener noreferrer"&gt;tweet us&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app/" rel="noopener noreferrer"&gt;How to create a white label iOS app (Part 1)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-2/" rel="noopener noreferrer"&gt;How to create a white label iOS app (Part 2)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-3/" rel="noopener noreferrer"&gt;How to create a white label iOS app (Part 3)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-4/" rel="noopener noreferrer"&gt;How to create a white label iOS app (Part 4)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-5/" rel="noopener noreferrer"&gt;How to create a white label iOS app (Part 5)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
    </item>
    <item>
      <title>How to create a white label iOS app (Part 3)</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Sat, 29 Aug 2020 15:17:43 +0000</pubDate>
      <link>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-3-14bm</link>
      <guid>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-3-14bm</guid>
      <description>&lt;p&gt;As we discussed on &lt;a href="https://dev.to/mavris/how-to-create-a-white-label-ios-app-part-2-240"&gt;Part2&lt;/a&gt;, it's not recommended to create  duplicates of the same class with different target membership.&lt;/p&gt;

&lt;p&gt;You can download the project &lt;a href="https://github.com/Rock-and-Null/iOS---WhiteLabel/releases/tag/Part2"&gt;here&lt;/a&gt; as we left it on Part2.&lt;/p&gt;

&lt;p&gt;We could tackle this issue using &lt;strong&gt;Preprocessor flags,&lt;/strong&gt; which will allow us to add conditions based on which target the app is running. Go ahead and delete the Coffee class under RealCoffee. Then go to Coffee class under the TestCoffee folder and check the RealCoffee target under &lt;strong&gt;Target Membership.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tCO0afGE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-28-at-8.36.47-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tCO0afGE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-28-at-8.36.47-PM.png" alt="How to create a white label iOS app (Part 3)"&gt;&lt;/a&gt;Coffee class is now recognised by both targets&lt;/p&gt;

&lt;p&gt;Now we need to set an identifier for each Target. Will set this identifier under Build Settings -&amp;gt; Active Compilation Condition for both identifiers. Use TESTCOFFEE and REALCOFFEE for both Debug and Release.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kzTO1BT0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-28-at-8.43.32-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kzTO1BT0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-28-at-8.43.32-PM.png" alt="How to create a white label iOS app (Part 3)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now head to the Coffee class and modify the coffeeDescription as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static func coffeeDescription() -&amp;gt; String {
     #if TESTCOFFEE
        return "TestCoffee"
     #elseif REALCOFFEE
        return "RealCoffee"
     #else
        return "WRONG"
     #endif
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It's clear from the code that the response from the coffeeDescription function will depend on the target. If the response is "WRONG" then something went wrong setting the &lt;strong&gt;Active Compilation Conditions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I personally don't like using Preprocessor flags for the coffeeDescription function because it won't scale. Imagine having 30 targets and having 30 Preprocessor flags for each one. I just wanted you to know that you have that option! Use with caution.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app/"&gt;How to create a white label iOS app (Part 1)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-2/"&gt;How to create a white label iOS app (Part 2)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-3/"&gt;How to create a white label iOS app (Part 3)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-4/"&gt;How to create a white label iOS app (Part 4)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-5/"&gt;How to create a white label iOS app (Part 5)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>xcode</category>
    </item>
    <item>
      <title>How to create a white label iOS app (Part 2)</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Mon, 24 Aug 2020 22:06:26 +0000</pubDate>
      <link>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-2-240</link>
      <guid>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-2-240</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_VK8ortt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1494537176433-7a3c4ef2046f%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_VK8ortt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1494537176433-7a3c4ef2046f%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" alt="How to create a white label iOS app (Part 2)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app/"&gt;How to create a white label iOS app (Part 1)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On Part 1 we talked about &lt;strong&gt;Targets&lt;/strong&gt; and how to use them in a white label product, but we noticed a rising problem. We couldn't differentiate (easily) the &lt;strong&gt;.plist&lt;/strong&gt; files for each target. So, we need to create a &lt;strong&gt;structure&lt;/strong&gt; to keep the non-shared files for each target.&lt;/p&gt;

&lt;p&gt;You can download the project &lt;a href="https://github.com/Rock-and-Null/iOS---WhiteLabel/releases/tag/Part2"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, we will create a folder called &lt;strong&gt;Resources&lt;/strong&gt; and then two subfolders called &lt;strong&gt;RealCoffee&lt;/strong&gt; and &lt;strong&gt;TestCoffee&lt;/strong&gt; (physical folders using &lt;strong&gt;New Group&lt;/strong&gt; option), and then we will place each &lt;strong&gt;.plist&lt;/strong&gt; under its corresponding target.&lt;/p&gt;

&lt;p&gt;Note: There is an extra &lt;strong&gt;.plist&lt;/strong&gt; file called &lt;strong&gt;TestCoffee copy-Info.plist&lt;/strong&gt;. Please delete it.&lt;/p&gt;

&lt;p&gt;Our project directory should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Adgi7syh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-24-at-7.44.59-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Adgi7syh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-24-at-7.44.59-PM.png" alt="How to create a white label iOS app (Part 2)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try to build and run the project you will notice that the Xcode is complaining about the Info.plist file. Since we renamed our Info.plist files, Xcode doesn't know where to find them so we need to set the path to the files. We will do that in &lt;strong&gt;Build Settings-&amp;gt;Info.plist file.&lt;/strong&gt; Just make sure to use the absolute value of the path. For RealCoffee is &lt;strong&gt;RealCoffee-Info.plist&lt;/strong&gt; and for TestCoffee is &lt;strong&gt;$(SRCROOT)/TestCoffee/Resources/TestCoffee/TestCoffee-Info.plist.&lt;/strong&gt; The reason is that the 2 files are located in different paths. We left it that way to demonstrate the difference.&lt;/p&gt;

&lt;p&gt;Our structure is fairly easy to understand. Any file that is bound only to one of the targets, it should fall under the target's folder. To illustrate, click on RealCoffee folder and create an object called &lt;strong&gt;Coffee&lt;/strong&gt; and set the target only to RealCoffee. Then, repeat the procedure for TestCoffee and set the target only to TestCoffee.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--By4LEcMi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-25-at-12.50.33-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--By4LEcMi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-25-at-12.50.33-AM.png" alt="How to create a white label iOS app (Part 2)"&gt;&lt;/a&gt;Two Coffee files with different target membership&lt;/p&gt;

&lt;p&gt;We will create 2 Coffee classes with different implementation in order to demonstrate how the targets interact with the files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Coffee {
    static func coffeeDescription() -&amp;gt; String {
        return "TestCoffee"
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
Coffee class for TestCoffee







&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Coffee {
    static func coffeeDescription() -&amp;gt; String {
        return "RealCoffee"
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
Coffee class for RealCoffee





&lt;p&gt;Now let us update the coffeeIBAction to call the Coffee static function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @IBAction func coffeeIBAction(_ sender: Any) {
        let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String
        let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
        self.popUpOptionDialog("",content: "You have ordered coffee using the \(appName!) app with version \(appVersion!) and coffee description: \(Coffee.coffeeDescription())")
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
Alert view in ViewController





&lt;p&gt;Now, if you run the app you will notice that the result for &lt;strong&gt;coffeeDescription()&lt;/strong&gt; is different for each target.&lt;/p&gt;

&lt;p&gt;We have used this example because it's easy to understand the fundamentals, but it's not recommended to create the same class with different target membership. There is a better way and we will demonstrate it in the next part!&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-2/"&gt;Original Article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app/"&gt;How to create a white label iOS app (Part 1)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-2/"&gt;How to create a white label iOS app (Part 2)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-3/"&gt;How to create a white label iOS app (Part 3)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-4/"&gt;How to create a white label iOS app (Part 4)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-5/"&gt;How to create a white label iOS app (Part 5)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>xcode</category>
    </item>
    <item>
      <title>How to create a white label iOS app (Part 1)</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Sat, 22 Aug 2020 11:02:39 +0000</pubDate>
      <link>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-1-4akp</link>
      <guid>https://forem.com/mavris/how-to-create-a-white-label-ios-app-part-1-4akp</guid>
      <description>&lt;p&gt;If you are a developer in a company that is developing software for its products only, then you probably won't be familiar with the "White-label" term.&lt;/p&gt;

&lt;p&gt;A white-label app is a product produced by one company then packaged and distributed by other companies under different brands.&lt;/p&gt;

&lt;p&gt;Let's say that are going to produce an app for coffee shops. The main functionality will be the same but we want to change the colours, icons, name, etc for each brand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Targets
&lt;/h3&gt;

&lt;p&gt;So, let's talk about &lt;strong&gt;Targets&lt;/strong&gt;. A target specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace. For example, we can create our TestCoffee app with its files, bundle ID, etc, and then we can create a new Target (called RealCoffee), that it will share the source code with TestCoffee. The name, bundle ID, and some other properties should be different between the two targets but luckily we can differentiate them easily in the Targets.&lt;/p&gt;

&lt;p&gt;Let's &lt;a href="https://github.com/Rock-and-Null/iOS---WhiteLabel/releases/tag/Part1"&gt;download&lt;/a&gt; the project and get our hands on the project.&lt;/p&gt;

&lt;p&gt;As you have already noticed, we have only one Target called TestCoffee. This is our test/abstract environment. Our first customer bought our software solution and it's called &lt;strong&gt;RealCoffee&lt;/strong&gt;. Now, we need to duplicate our TestCoffee target:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MyzbdE0p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-22-at-12.48.16-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MyzbdE0p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-22-at-12.48.16-PM.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we rename Display Name and Bundle Identifier to &lt;strong&gt;RealCoffee&lt;/strong&gt; and &lt;strong&gt;rockandnull.com.RealCoffee&lt;/strong&gt; accordingly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FGjSKfeZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-22-at-12.57.13-PM-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FGjSKfeZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-22-at-12.57.13-PM-4.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also need to rename the scheme so it will be easier to match the target with the scheme ( &lt;strong&gt;TestCoffee copy&lt;/strong&gt; should be rename to &lt;strong&gt;RealCoffee&lt;/strong&gt; ).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8Kcju7Kc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-22-at-1.00.25-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8Kcju7Kc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rockandnull.com/content/images/2020/08/Screenshot-2020-08-22-at-1.00.25-PM.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You probably have already noticed one new .plist file called &lt;strong&gt;TestCoffee copy 2-Info.plist&lt;/strong&gt; that belongs to RealCoffee target, but don't worry we will come back to this later.&lt;/p&gt;

&lt;p&gt;Now, run the app using the TestCoffee target and tap the "I want coffee" button. Then, switch to RealCoffee target and tap the "I want coffee". You will notice the popup message is slightly different between the two targets.&lt;/p&gt;

&lt;p&gt;This is how easy is to use &lt;strong&gt;Targets&lt;/strong&gt; in your project! Of course, things could get messy pretty fast in a real project, but don't worry, we are here to cover these scenarios in the next parts.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app/"&gt;How to create a white label iOS app (Part 1)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-2/"&gt;How to create a white label iOS app (Part 2)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-3/"&gt;How to create a white label iOS app (Part 3)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-4/"&gt;How to create a white label iOS app (Part 4)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.rockandnull.com/how-to-create-a-white-label-ios-app-part-5/"&gt;How to create a white label iOS app (Part 5)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>xcode</category>
    </item>
    <item>
      <title>Quick UI Android tests: Espresso Test Recorder + common modifications</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Thu, 07 May 2020 16:44:22 +0000</pubDate>
      <link>https://forem.com/mavris/quick-ui-android-tests-espresso-test-recorder-common-modifications-4l9j</link>
      <guid>https://forem.com/mavris/quick-ui-android-tests-espresso-test-recorder-common-modifications-4l9j</guid>
      <description>&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%2Fimages.unsplash.com%2Fphoto-1572071420940-ca9829fe94d0%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" 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%2Fimages.unsplash.com%2Fphoto-1572071420940-ca9829fe94d0%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" alt="Quick UI Android tests: Espresso Test Recorder + common modifications"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Testing is super important for providing software that is reliable and this is widely accepted. Unfortunately, testing is hard. It requires proper architecture, as well as significant time to write the tests (quite often it takes more time than the actual code).&lt;/p&gt;

&lt;p&gt;Espresso tests are the UI integration tests for Android apps. They run on a device or emulator. Normally, you need to write these tests by hand, just like you would with a regular unit test. Android Studio's Espresso Test Recorder is a valuable tool to cut down the time required to write such a test. It's completely visual and provides a great starting point!&lt;/p&gt;

&lt;p&gt;To record a test, connect a device or launch an emulator instance, and select &lt;code&gt;Run -&amp;gt; Record Espresso Test&lt;/code&gt; in Android Studio. Your app will launch and whatever clicks you make on the emulator/device, they will be recorded.&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%2Fwww.rockandnull.com%2Fcontent%2Fimages%2F2020%2F05%2FScreenshot-2020-05-06-at-17.43.39.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%2Fwww.rockandnull.com%2Fcontent%2Fimages%2F2020%2F05%2FScreenshot-2020-05-06-at-17.43.39.png" alt="Quick UI Android tests: Espresso Test Recorder + common modifications"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you want to verify that something you expect is indeed on the screen, click the &lt;code&gt;Add Assertion&lt;/code&gt; button. What is currently on the device screen, will appear in that window. You can click on an element, and assert what you expect.&lt;/p&gt;

&lt;p&gt;When you are done, click &lt;code&gt;OK&lt;/code&gt; and a Kotlin/Java integration test will appear!&lt;/p&gt;

&lt;h3&gt;
  
  
  Common modifications needed
&lt;/h3&gt;

&lt;p&gt;Although this looks like magic, it's not. This tool is complementary to writing UI tests. It will output a complete test, but most probably you would need to modify it to make sure it's not flaky and works all the time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use fakes/mocks&lt;/strong&gt;
By default the test recorder will use the real implementation of your classes. This means that you will connect to your production instance and make changes to your production data. This is not ideal, to say the least, and would lead to flaky tests (i.e. sometimes the test might pass, sometimes not).
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make your tests hermetic (i.e. self-sufficient tests that produce reproducible results) you would need to replace some parts of your stack with fake implementations that always return the same results (to have reproducible tests). Services that connect to a remote server and return results are the most obvious ones.  &lt;/p&gt;

&lt;p&gt;An excellent guide for deep diving and a practical example &lt;a href="https://android.jlelse.eu/espresso-tests-from-0-to-1-e5c32c8a595" rel="noopener noreferrer"&gt;here&lt;/a&gt;.   &lt;/p&gt;

&lt;p&gt;If you don't have a proper architecture for faking out real implementations for fakes, you can use an Espresso test to automate things that you would be testing manually, until you properly refactor your architecture.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idle resources&lt;/strong&gt;
Another big issue with UI tests and the recorder is that it does not figure out when it should wait before taking a UI action (e.g. clicking a button). While recording the test, the waiting time before clicking on a button is not recorded (e.g. waiting for that spinner to disappear before proceeding is not recorded).
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The proper solution is to "teach" Espresso when a resource is ready and action can be taken (more details in the &lt;a href="https://developer.android.com/training/testing/espresso/idling-resource" rel="noopener noreferrer"&gt;official doc&lt;/a&gt;).   &lt;/p&gt;

&lt;p&gt;An ugly (but working) hack I found some time ago, is to use a custom action and wait for the desired view to appear before proceeding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class WaitViewAction private constructor(private val viewId: Int, private val millis: Long): ViewAction {

    companion object {
        fun waitId(viewId: Int, millis: Long = 5000) {
            onView(isRoot()).perform(WaitViewAction(viewId, millis))
        }
    }

    override fun getConstraints(): Matcher&amp;lt;View&amp;gt; {
        return isRoot()
    }

    override fun getDescription(): String {
        return "Waiting for $viewId"
    }

    override fun perform(uiController: UiController, view: View?) {
        uiController.loopMainThreadUntilIdle()
        val startTime = System.currentTimeMillis()
        val endTime = startTime + millis
        val viewMatcher = withId(viewId)
        do {
            for (child in TreeIterables.breadthFirstViewTraversal(view)) {
                if (viewMatcher.matches(child)) {
                    return
                }
            }
            uiController.loopMainThreadForAtLeast(50)
        } while (System.currentTimeMillis() &amp;lt; endTime)
        throw IllegalStateException("View waiting timed out")
    }
}

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

&lt;/div&gt;



&lt;p&gt;Then use like this: &lt;code&gt;WaitViewAction.waitId(R.id.myButtonView)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Matchers&lt;/strong&gt;
Noticed that the recorder quite often is adding more matching conditions than necessary. Therefore, if you just recorded a test then run it and fails, you might need to manually inspect the code that tries to find and click on elements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully, this was a super quick introduction to the &lt;a href="https://developer.android.com/training/testing/ui-testing/espresso-testing" rel="noopener noreferrer"&gt;Espresso&lt;/a&gt; Test Recorder and the common things that you might need to change after you record a test. Happy testing!&lt;/p&gt;

</description>
      <category>android</category>
      <category>espresso</category>
    </item>
    <item>
      <title>How to parse JSON in Swift 5 (with Codable)</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Sat, 02 May 2020 07:47:15 +0000</pubDate>
      <link>https://forem.com/mavris/how-to-parse-json-in-swift-5-with-codable-386i</link>
      <guid>https://forem.com/mavris/how-to-parse-json-in-swift-5-with-codable-386i</guid>
      <description>&lt;p&gt;Parsing &lt;code&gt;JSON&lt;/code&gt; it's a task that you will need to implement almost in every project that interacts with a &lt;code&gt;REST API&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For this example, we are going to use &lt;code&gt;Twitter API&lt;/code&gt; to get statuses class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"created_at": "Wed Oct 10 20:19:24 +0000 2018",
"id": 1050118621198921728,
"id_str": "1050118621198921728",
"text": "To make room for more expression, we will now count all emojis as equal—including those with gender‍‍‍ and skin t… https://t.co/MkGjXf9aXm",
"lang": "en",
"user": {
        "id": 6253282,
        "description": "The Real Twitter API. Tweets about API changes, service issues and our Developer Platform. Don't get an answer? It's on my website.",
        "url": "https://t.co/8IkCzCDr19",
        "name":"Twitter API"
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JSONSerialisation&lt;/strong&gt;&lt;br&gt;
We have the native option which is to use the &lt;code&gt;JSONSerialisation&lt;/code&gt; class method &lt;code&gt;jsonObject(with:options:)&lt;/code&gt; which returns a value of type &lt;code&gt;Any&lt;/code&gt;. Then you will most probably cast it in &lt;code&gt;[String:Any]&lt;/code&gt; in order to access the keys and after getting the value of a specific key you should cast it again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if let statusesArray = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]],
    let user = statusesArray[0]["user"] as? [String: Any],
    let username = user["name"] as? String {
    // Finally we got the username
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.rockandnull.com/how-to-parse-json-in-swift-5/"&gt;&lt;strong&gt;Continue reading on my personal blog&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>xcode</category>
      <category>swift</category>
      <category>json</category>
    </item>
    <item>
      <title>Into the Flow: Kotlin cold streams primer</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Thu, 30 Apr 2020 19:02:55 +0000</pubDate>
      <link>https://forem.com/mavris/into-the-flow-kotlin-cold-streams-primer-128i</link>
      <guid>https://forem.com/mavris/into-the-flow-kotlin-cold-streams-primer-128i</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jz1mx5Fq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1455577380025-4321f1e1dca7%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jz1mx5Fq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1455577380025-4321f1e1dca7%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D2000%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" alt="Into the Flow: Kotlin cold streams primer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are a regular user in the Kotlin community you must have heard about Kotlin Flows. At least, I've heard about them but never spend the time looking into them. But lately, when I was about to start a new Android project I decided it was finally time to look into them. Maybe everybody was talking about them for a reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Flows
&lt;/h3&gt;

&lt;p&gt;We already have suspending functions. We could do async operations without blocking, by using &lt;code&gt;suspend fun doSomething(): List&amp;lt;Something&amp;gt;&lt;/code&gt; for instance. But that would mean that we need to calculate all the results before starting to process them. Using &lt;code&gt;fun doSomething(): Flow&amp;lt;Something&amp;gt;&lt;/code&gt; we can process the results as soon as they are ready in a non-blocking way.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's Cold Flows actually
&lt;/h3&gt;

&lt;p&gt;You might have noticed that the function returning the &lt;code&gt;Flow&lt;/code&gt; does not have the &lt;code&gt;suspend&lt;/code&gt; prefix. This is because it returns immediately and doesn't do any processing. The actual work happens when the flow is "collected" (or more generally speaking a terminal operator is applied on the flow), e.g. &lt;code&gt;flow.collect { something -&amp;gt; println(something) }&lt;/code&gt;. Every time we call &lt;code&gt;collect&lt;/code&gt;, the flow code will be executed again.&lt;/p&gt;

&lt;p&gt;It's referred to as "cold", because the flow (or stream if you like) doesn't exist before &lt;code&gt;collect&lt;/code&gt; is called and is created again every time &lt;code&gt;collect&lt;/code&gt; is called. If the flow was there anyway and we just listened to it, then it would be "hot".&lt;/p&gt;

&lt;h3&gt;
  
  
  Create and terminate a Flow
&lt;/h3&gt;

&lt;p&gt;The most basic way to create a Flow is using the &lt;code&gt;flow { ... }&lt;/code&gt; builder (unexpected, right?)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun doSomething(): Flow&amp;lt;Int&amp;gt; = flow { 
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A new item in the flow can be "emitted" using &lt;code&gt;emit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Code inside the &lt;code&gt;flow { ... }&lt;/code&gt; builder block can suspend (&lt;code&gt;delay&lt;/code&gt; is a &lt;code&gt;suspend&lt;/code&gt; function). But, as we said before, &lt;code&gt;doSomething()&lt;/code&gt; is not a &lt;code&gt;suspend&lt;/code&gt; function and doesn't block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val something = doSomething()

viewModelScope.launch {
    something.collect { value -&amp;gt; println(value) }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;On the other hand, &lt;code&gt;collect&lt;/code&gt; is a suspend function and needs to run inside a &lt;a href="https://dev.to/mavris/coroutines-a-practical-vocabulary-3ok5-temp-slug-6341091"&gt;Coroutine Scope&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Other ways to create a Flow include converting regular collections (e.g. &lt;code&gt;listOf(1,2,3).asFlow()&lt;/code&gt;) or fixed set of items into Flows (e.g. &lt;code&gt;(1..3).asFlow()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;collect&lt;/code&gt; is just one way to terminate a Flow. Other ways include getting the &lt;code&gt;first&lt;/code&gt;, ensuring only a &lt;code&gt;single&lt;/code&gt; value is emitted, &lt;a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/reduce.html"&gt;reducing&lt;/a&gt;, &lt;a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/fold.html"&gt;folding&lt;/a&gt;, converting &lt;code&gt;toList&lt;/code&gt;, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transforming Flows
&lt;/h3&gt;

&lt;p&gt;Transforming is quite similar to how you would transform a regular collection. Familiar operators such as &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;take&lt;/code&gt; exist. I think there are 2 major differences from the regular collection transformation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code inside those transformation functions can &lt;code&gt;suspend&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You don't have to return a single value, you can call &lt;code&gt;emit&lt;/code&gt; as many times as you want using &lt;code&gt;transform&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(1..3).asFlow() 
    .transform { number -&amp;gt;
        emit(number*2)
        delay(100)
        emit(number*4) 
    }
    .collect { number -&amp;gt; println(number) }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Coroutine Scope
&lt;/h3&gt;

&lt;p&gt;When we &lt;code&gt;collect&lt;/code&gt; a Flow we are executing a &lt;code&gt;suspend&lt;/code&gt; function. This means that both the code inside &lt;code&gt;flow { ... }&lt;/code&gt; and inside &lt;code&gt;collect { ... }&lt;/code&gt; will run on the current &lt;a href="https://dev.to/mavris/coroutines-a-practical-vocabulary-3ok5-temp-slug-6341091"&gt;Coroutine Scope&lt;/a&gt;. But there are cases we want those 2 code blocks to run in different Coroutine Context (usually to run them in different Dispatchers, for not blocking the Main/UI thread for instance).&lt;/p&gt;

&lt;p&gt;We &lt;strong&gt;shouldn't&lt;/strong&gt; use the familiar &lt;code&gt;withContext(Dispatchers.IO)&lt;/code&gt; in this case. Flows provide a dedicated &lt;code&gt;flowOn&lt;/code&gt; operator for this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun doSomething(): Flow&amp;lt;Int&amp;gt; = flow { 
    // This runs on Dispatchers.IO
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}.flowOn(Dispatchers.IO)

[...]

viewModelScope.launch {
    doSomething().collect { value -&amp;gt;
        // This runs on Main thread and just prints those values
        print (value)
    } 
}    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Composing Flows
&lt;/h3&gt;

&lt;p&gt;One of the most powerful things you can do with Flows it to compose them in different ways.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;zip&lt;/code&gt; them, i.e. combine the &lt;strong&gt;1st&lt;/strong&gt; item of &lt;strong&gt;Flow A&lt;/strong&gt; with the &lt;strong&gt;1st&lt;/strong&gt; item of &lt;strong&gt;Flow B&lt;/strong&gt; , etc
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val flowA = (1..3).asFlow()
val flowB = flowOf("one", "two", "three")
flowA.zip(flowB) { a, b -&amp;gt; "$a and $b" }
     .collect { println(it) }

// Output:
// 1 and one
// 2 and two
// 3 and three
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;combine&lt;/code&gt; them, i.e. every time a new value is emitted from &lt;strong&gt;Flow A&lt;/strong&gt; combine it with the latest value of &lt;strong&gt;Flow B&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val flowA = (1..3).asFlow()
val flowB = flowOf("single item")
flowA.combine(flowB) { a, b -&amp;gt; "$a and $b" }
     .collect { println(it) }

// Output:
// 1 and single item
// 2 and single item
// 3 and single item
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Flattening Flows
&lt;/h3&gt;

&lt;p&gt;Getting to the situation where a Flow transforms into another "sub"-Flow and you end up with &lt;code&gt;Flow&amp;lt;Flow&amp;lt;X&amp;gt;&amp;gt;&lt;/code&gt; is quite common. Similarly to collections, there is a number of &lt;code&gt;flat*&lt;/code&gt; operations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flatMapConcat&lt;/code&gt; for waiting for each "sub"-Flow to complete before collecting (i.e. collecting Flows sequentially)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flatMapMerge&lt;/code&gt; for collecting whatever item is emitted (i.e. collecting the Flows in parallel)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flatMapLatest&lt;/code&gt; for collecting the latest "sub"-Flow that is emitted, while canceling the previous "sub"-Flow&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Exceptions
&lt;/h3&gt;

&lt;p&gt;Whatever exception is thrown in &lt;code&gt;flow { ... }&lt;/code&gt; or &lt;code&gt;collect { ... }&lt;/code&gt; can be caught with regular &lt;code&gt;try - catch&lt;/code&gt; statements.&lt;/p&gt;

&lt;p&gt;But there is a more declarative way, with the &lt;code&gt;catch&lt;/code&gt; operator. Note that when you "catch" the exception, you can &lt;code&gt;emit&lt;/code&gt; again, &lt;code&gt;throw&lt;/code&gt;, log, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doSomethingThatMightThrow()
    .catch { e -&amp;gt; emit("Caught $e but emitting something else") }
    .collect { value -&amp;gt; println(value) }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Another important thing to remember is that this operator is "upstream" only, i.e. it does not "catches" anything after it's declared.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doSomethingThatMightThrow()
    .catch { e -&amp;gt; emit("Caught $e but emitting something else") }
    .collect { 
        // Something gone wrong here, will not be caught
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;My impression after looking into Flows is that they can handle more than the basic "cold stream" needs. They are not as comprehensive and powerful as other reactive libraries (e.g. RxJava) but you can always add them to your project later when needed (actually converters between Flows and other reactive libraries are &lt;a href="https://kotlinlang.org/docs/reference/coroutines/flow.html#flow-and-reactive-streams"&gt;provided&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For those of you wanting to read more on the subject, I would recommend the &lt;a href="https://kotlinlang.org/docs/reference/coroutines/flow.html"&gt;official docs&lt;/a&gt; and these blog posts with insights into the design of Flows (&lt;a href="https://medium.com/@elizarov/reactive-streams-and-kotlin-flows-bfd12772cda4"&gt;1&lt;/a&gt;, &lt;a href="https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9"&gt;2&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>android</category>
      <category>flows</category>
    </item>
    <item>
      <title>Transferable knowledge</title>
      <dc:creator>Michael Mavris</dc:creator>
      <pubDate>Wed, 15 Feb 2017 20:23:50 +0000</pubDate>
      <link>https://forem.com/mavris/software-developmenttransferable-knowledge</link>
      <guid>https://forem.com/mavris/software-developmenttransferable-knowledge</guid>
      <description>&lt;p&gt;This is a story about my knowledge as an iOS Developer and how I transferred that knowledge to a different language and SDK.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The path to become an iOS Developer&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Back in 2008 when Steve Jobs announced iOS SDK (I was still studying Computer Science) I made the most important choice of my life so far. “I will become an iOS Developer I thought to myself.&lt;/p&gt;

&lt;p&gt;Having no experience as Software Developer, the path to become a professional iOS Developer was not easy at all. Long story short, I landed my first job as iOS Developer two years ago.&lt;/p&gt;

&lt;h4&gt;
  
  
  My concerns
&lt;/h4&gt;

&lt;p&gt;There is a controversial saying: “Jack of all trades, master of none which I totally agree. It implies that by trying to learn a lot of things, you won't master any of them. I consider myself as “jack of all but not by choice. I previously worked as Systems Engineer for five years and after switching to iOS Developer, I was working on web projects when there was no iOS projects in the queue. So, I can build a full product myself but still, I do not master any of these technologies. So I wanted to focus on iOS only and master that field.&lt;/p&gt;

&lt;p&gt;My biggest concern was: &lt;em&gt;What will happen if iOS devices reduced to 1% and no one wants an iOS app?&lt;/em&gt; I know I can easily transfer my knowledge to macOS apps but nobody asks for macOS developers in my country. So, what I learned so far will be useless, right?&lt;/p&gt;

&lt;h4&gt;
  
  
  It depends!
&lt;/h4&gt;

&lt;p&gt;It depends on your experience the time you decided to focus on that specific language and SDK. For example, if you don't have experience in multithreading, working withs GCD, NSOperations, NSThread etc (multithreading in iOS and macOS), will help you understand the concept. If you go deeper you will learn about Race Conditions, DeadLocks, Atomicity etc. On the other hand if you are already experienced with multithreading going deep on GCD or NSOperations, wouldn't be helpful for other languages and SDKs.&lt;/p&gt;

&lt;h4&gt;
  
  
  From iOS Developer to Mobile Developer
&lt;/h4&gt;

&lt;p&gt;So, I got this offer for Xamarin Developer that I couldn't refuse. I thought that C# is very good language and also I will get the chance to get familiar with Android OS, so why not?&lt;/p&gt;

&lt;h4&gt;
  
  
  Transferable knowledge
&lt;/h4&gt;

&lt;p&gt;As I said before, Objective-C and iOS were the first language and SDK that I went deep. So, I didn't transfer any knowledge and I learnt everything from scratch.&lt;/p&gt;

&lt;h4&gt;
  
  
  Software Design Patterns
&lt;/h4&gt;

&lt;p&gt;Coming from university's C style programming I had no idea what Software Design Patterns means(in real software). Creating a couple of projects in Objective-C helped me to undestartand the concept.&lt;/p&gt;

&lt;p&gt;Apple is using a poor implementation of &lt;a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller"&gt;MVC&lt;/a&gt; in their examples. After building some big projects I realised that Apple's way of building apps (at least how they build their examples) wasn't suitable for me and I looked for alternatives Software Design Patterns. Then I got familiar with &lt;a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter"&gt;MVP&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel"&gt;MVVM&lt;/a&gt;. I ended up using MVVM.&lt;/p&gt;

&lt;p&gt;This kind of knowledge is fully transferable to other languages and SDKs. For example, to build an app for iOS and Android with shared business logic, MVVM is the way to go.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Object Oriented Programming&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Again, coming from procedural C, I was using classes as structs in Objective-C. For example, if I had a class Movie and I needed to manipulated the data, I would probably do it in the ViewController. But, as the project was growing, I would need the manipulated data on another ViewController also and I realised that this “logic should be handled by the class.&lt;/p&gt;

&lt;p&gt;Object Oriented Programming is also fully trasferable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Multithreading
&lt;/h4&gt;

&lt;p&gt;Multithreading approaches might be different for each language. There are some low and high level APIs. I didn't go very deep on multithreading in iOS, but I am familiar with Race Conditions, DeadLocks, Atomicity etc. This knowledge can be applied to other languages and SDKs as well.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mobile way of thinking
&lt;/h4&gt;

&lt;p&gt;The approach for building a mobile app is very different from building a website.&lt;/p&gt;

&lt;p&gt;First of all you need to be careful what data are presented to the user. Since the screens are small, there is no need to present unnecessary data in a List View.&lt;/p&gt;

&lt;p&gt;You also need to consider the resources of the mobile devices. For example a user might be using cellular data, so downloading 10 MB of data each time your app starts wouldn't be a good idea.&lt;/p&gt;

&lt;p&gt;Some other tricks like showing cached data to the user while the new data are beeing downloaded, will keep the user busy without having him/her watching a progress bar.&lt;/p&gt;

&lt;p&gt;In this case the knowledge is transferable only because I am moving from mobile to mobile SDK.&lt;/p&gt;

&lt;h4&gt;
  
  
  Version Control
&lt;/h4&gt;

&lt;p&gt;When I started using Git, I was only using Commit, Push, Pull. Then I learnt about Branches, Tags, Conflicts, Pull requests etc and I developed my own flow. This knowledge is also transferable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;In my case, I was able to transfer about 80% of my knowledge to a new language and SDK. Because I was totally inexperienced before starting as iOS developer, I was able to transfer a lot of basic knowledge. It also helps that I moved from mobile to mobile SDK.&lt;/p&gt;

&lt;p&gt;I am still learning new things with C# (like Inversion of Control) that I could apply in other languages in the future.&lt;/p&gt;

&lt;p&gt;So, what knowledge you can transfer, depends on your experience and if you are moving to a similar SDK.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
