iSpective

Igor on tech and life

Setting up Fastlane for mobile automatization on Mac OS

Fastlane is used to automatize different routines in mobile development. In this note (or serious of notes) I'll describe how to use Fastlane to automize your iOS project builds and uploads to TestFlight.

You start by installing latest Xcode tools
xcode-select --install

Next, you install Fastlane via RubyGems
sudo gem install fastlane -NV

or via brew
brew cask install fastlane

Then cd to your project and initialize Fastlane:
fastlane init

Last, edit fastlane/Fastfile to this:

platform :ios do

  before_all do
    ENV["FASTLANE_DONT_STORE_PASSWORD"] = "1"
    ENV["FASTLANE_USER"] = "<Your App Store Connect email"
  end

  desc "Build and upload to TestFlight"
  lane :beta do
        build_app(scheme: "<Your project's scheme>",
                      workspace: "<Your project's>.xcworkspace",
                  include_bitcode: true)
        upload_to_testflight
  end
end

If you want to store your password in the Keychain, just remove ENV["FASTLANE_DONT_STORE_PASSWORD"] = "1"

If you want to store your password in the Fastfile, add ENV["FASTLANE_PASSWORD"] = "<yourPassword>" into the before_all do / end section.

Now run 'fastlane beta' in your Terminal and enjoy an automatic build and upload to TestFlight 🙂

You can use this manual on your own computer. For running it on a remote machine look out for part 2 of this series.

Autostart scripts and services on Mac OS with launchd

When it comes to autostarting custom scripts/services after booting your Mac, adding stuff to start with your system on Mac OS might now be always possible via System Preferences.app -> Users & Groups -> Login Items. [1]

So in order to make your script or service to launch on start, follow my example on running a DynDNS service on boot and each 5 minutes afterwards.

First start by creating a shell script that launches your service. In my case that's a script that curl's a specific url with parameters of my current IP to assign it to the domain I'm using:
nano ~/Documents/dyndns.sh

with:

IP=$(curl ifconfig.co)
curl "https://dynamicdns.park-your-domain.com/update?host=@&domain=example.com&$

Next I create a system service plist:
nano /Library/LaunchDaemons/dyndns.plist

And enter the following:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
        <dict>
                <key>Label</key>
                <string>com.igor.dyndns</string>
                <key>ProgramArguments</key>
                <array>
                <string>/bin/sh</string>
                <string>/Users/igor/Documents/dyndns.sh</string>
                </array>
                <key>RunAtLoad</key>
                <true/>
                <key>StartInterval</key>
                <integer>300</integer>
        </dict>
</plist>

You can avoid the StartInterval key if what you need is just to load on start. You also might label your service something else than com.igor.dyndns

If you're launching your service via shell or you just have a shell script, you'll have to add the absolute path to it via <string>/bin/sh</string> followed by the absolute path to your script within <key>ProgramArguments</key>. If you're not using shell, you just have to specify what you're launching using absolute paths.

My personal recommendation is to specify one launch script here and enter eveything what needs to be launched to that script and not to the plist itself.

Last step is to load your custom service to launchd via:
sudo launchctl load -w /Library/LaunchDaemons/dyndns.plist


  1. Since we're here, you can visit these settings in order to remove something unwanted from your startup which might got there without your consent and might actually slow down your system startup ↩ī¸Ž

How to use PPTP VPN on Mac OS Sierra and later

In iOS 10 and Mac OS Sierra Apple removed support for PPTP VPNs from their major OSes and technically had the right for it since PPTP is not secure and outdated. But in case you still have the need to connect to a VPN that works only via PPTP and you're rocking Mac OS Sierra or later you're out of luck. Unless you try using Shimo or Flow VPN which both for me didn't work at all, you're really out of luck 🙂

Thankfully guys at Apple removed only the GUI part of the PPTP client, and you still can use the pppd daemon throught Terminal. But before that you'll have to create a configuration first:
sudo nano /etc/ppp/peers/vpn.example.com

Next, fill it with this info, replacing vpn info with yours:

plugin PPTP.ppp
noauth
# logfile /tmp/ppp.log
remoteaddress "vpn.example.com"
user "username"
password "password"
redialcount 1
redialtimer 5
idle 1800
# mru 1368
# mtu 1368
receive-all
novj 0:0
ipcp-accept-local
ipcp-accept-remote
# noauth
refuse-eap
refuse-pap
refuse-chap
refuse-chap-md5
refuse-mschap
hide-password
mppe-stateless
mppe-128
# require-mppe-128
looplocal
nodetach
# ms-dns 8.8.8.8
usepeerdns
# ipparam gwvpn
defaultroute
debug

Save the file and then start your connection via:
sudo pppd call vpn.example.com

To stop the deamon, close the Terminal with the PPTP session, open a new one and enter:
sudo killall pppd

Saving array of custom objects to SharedPreferences on Android

On iOS when you store an object in UserDefaults, you just put it there via setValueForKey, and it doesn't matter if you put the class there on its own, or an array of your custom classes. What matters is when you get it (them) back via objectForKey or arrayForKey, you cast the object(s) into a correct type/array of types.

On Android the principle is similar with the primitives (ints, strings, booleans, etc.) but it gets a bit tricky from an iOS-dev's perspective to store an array of objects in SharedPreferences (Android's analog for iOS's UserDefaults), but there is a solution.

First, we save your array (list) of objects:

// Needed libraries
import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;

// Accessing SharedPreferences
SharedPreferences sp = getSharedPreferences("MyPreferences", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();

// Saving accounts
public void saveAccounts(List<Account> accounts) {
    Gson gson = new Gson();
    String accountsJson = gson.toJson(accounts);
    editor.putString(r.getString("SavedAccounts"), accountsJson);
    editor.apply();
}

As you can see, even though we pass an array of objects, we then convert and store them actually as a string. Nothing unusual here, except storing an array of objects as a string 🙂

Where it gets interesting is when we need to get the objects back from SharedPreferences:

public List<Account> getAccounts() {
    Gson gson = new Gson();
    Type type = new TypeToken<List<Account>>(){}.getType();
    String accountsString = sharedPref.getString("SavedAccounts", "");
    return gson.fromJson(accountsString, type);
}

Here we get our array of objects as a string and convert it back to our list of accounts via Gson's built-in functionality.

Universal data+notification FCM payload for iOS+Android

In case you're following my FCM payload saga, here's a happy ending that covers most usecases with one payload which will allow you sending basic FCM messages with data and notification in them. And such messages are universal and will produce the same result on both iOS and Android. On iOS this will work out of the box, whereas on Android a small hack is neccessary to be added to the app itself.

With the payload below, you'll be able send a unified FCM message to iOS and Android and on both platforms you will receive a notifcation pop-up/heads-up message as well as pass some data to the callback method of the app which then may use it to do something meaningful in the background:

{  
   "message":{  
      "token":"<device registration id>",
      "apns":{
         "payload":{
            "aps":{
               "content-available":1,
               "alert":{
                  "title":"title",
                  "subtitle":"subtitle",
                  "body":"body"
               },
               "badge":7,
               "sound":"default"
            }
         }
      },
      "data":{
         "account":{
            "first-name":"Igor",
            "last-name":"Z"
         },
         "androidTitle":"title",
         "androidBody":"body"
      }
   }
}

iOS will use the apns part to display the notification, and pass data=>account to your app for further use. And essentially ignore the androidTitle and androidBody part.

Android on the other hand will ignore the whole apns branch, pass data=>account to the app as well and with the small hack mentioned above use androidTitle and androidBody to display a local notification.

That's a lot of power in a single payload. I hope you find it helpful 🙂

Combining data+notification payload in FCM Android

If you send this payload to an iOS device via FCM, and also invoke UNUserNotificationCenter's didReceive response: withCompletionHandler callback method, iOS will both display a text notification as well as allow you app some background time to deal with the data object you're sending along your push notification.

Unfortunately on Android even using my recommended payload you won't achieve this behavior. If you send both notification and data, only the notification will be shown, and the OnMessageReceived callback method won't be called to handle the data object.

But if you're in charge of the FCM payload creation, you can put the notification's title and body into the data object like this:

{  
   "message":{  
      "token":"<device registration id>",
      "data":{
         "account":{
            "first-name":"Igor",
            "last-name":"Z"
         },
         "androidTitle":"title",
         "androidBody":"body"
      }
   }
}

And when OnMessageReceived is called with the contents of this payload, you just create a local heads-up notification based on the info in androidTitle and androidBody with this code:

This code is a bit verbose, but is guaranteed to be working on all current versions of Android, which is invaluable 🙂 You just put androidTitle and androidBody as parameters of the method above and a nice default nofitication will appear on each message.

Now you can enjoy notification+data push notifications in a single message on Android with this hack, instead of sending two messages each time.