Friday, May 15, 2020

Giving Ionic's Capacitor a Dev Friendly Boost

I recently started a Capacitor based Ionic project. After years of working with Cordova, the framework initally felt jarring. However, with even a bit of use, I'm already buying into Capacitor's benefits. The Cordova approach of generating Android and iOS projects often worked great, but at times the approach showed itself to be quite fragile. Something would change in the iOS world and you'd be at the mercy of Cordova to update the build process to catch up with this. Not so with Capacitor, which embraces the notion of working directly on Android and iOS native projects.

I do however miss a number of nicities that Cordova offered. Take versioning. It seems obvious that there would be a central location where your project's version number was set and the iOS and Android apps pulled from this. And yet, this isn't the case with Capacitor. The thought of having to launch two different IDE's to manually change the app's version number seems like a recipe for disaster.

Speaking of launching IDE's, the thought of giving up command line building and running of apps is another step backwards.

Fortunately, with a bit of shell scripting the versionining and building challenge for Android can be addressed. Building from the command line on iOS is possible but it will take a bit more research befor I get it figured out. I added this functionality to my ionicassist command line tool. It's now possible to say:

  ionicassist android-cap
  ionicassist ios-cap

To push the version number found in capacitor.config.json to both Android and iOS and build and install the Android project from the command line. The source code for ionicassist is below. Most of the code is straightfoward, though dealing with the ridiculous Info.plist format took some work. xmlstarlet and jq both proved invaluable for this little project. 

#!/bin/bash

##
## Scrip to help with ionic dev
##

action=$1 ; shift
case $(uname) in 
  Darwin)
    PLATFORM_TOOLS=$HOME/Library/Android/sdk/platform-tools/
    APK_DEST="$HOME/Google Drive (ben@ideas2executables.com)/Clients/Uchi/APKs/"
    ;;
  
  CYGWIN_NT-10.0)
    PLATFORM_TOOLS=/d/tools/android/platform-tools/
    APK_DEST="/d/Google_Drive_i2x/Clients/Uchi/APKs/"
    ;;
esac

ADB=$PLATFORM_TOOLS/adb

case "$action" in
  grab-apk)
    name=$(xmlstarlet sel -N x=http://www.w3.org/ns/widgets  -t -v '/x:widget/x:name' config.xml | sed 's/[^A-Za-z0-9_-]//g')
    version=$(xmlstarlet sel -N x=http://www.w3.org/ns/widgets  -t -v '/x:widget/@version' config.xml)
    cp -v platforms/android/app/build/outputs/apk/debug/app-debug.apk "$APK_DEST/$name-$version.apk"
    ;;

  grab-release-apk)
    name=$(xmlstarlet sel -N x=http://www.w3.org/ns/widgets  -t -v '/x:widget/x:name' config.xml | sed 's/[^A-Za-z0-9_-]//g')
    version=$(xmlstarlet sel -N x=http://www.w3.org/ns/widgets  -t -v '/x:widget/@version' config.xml)
    cp -v platforms/android/app/build/outputs/apk/release/app-release.apk "$APK_DEST/$name-$version.apk"
    ;;

  screencap)
    if [ -z "$1" ] ; then
      echo "Usage: $(basename $0) screencap {file}"
      exit
    else
      file=$1 ; shift
      $ADB shell screencap /sdcard/screen.png
      $ADB pull /sdcard/screen.png $file
    fi
    ;;


  screenrec)
    if [ -z "$1" -o -z "$2" ] ; then
      echo "Usage: $(basename $0) screencap {file} {duration}"
      exit
    else
      file=$1 ; shift
      duration=$1 ; shift
      $ADB shell screenrecord --verbose --time-limit $duration /sdcard/screen.mp4
      $ADB pull /sdcard/screen.mp4 $file
    fi
    ;;

  adb)
    exec $ADB "$@"
    ;;

  sync-cap)
    cap_conf=capacitor.config.json
    and_conf=android/app/build.gradle
    ios_conf=ios/App/App/Info.plist
    if [ -f "$cap_conf" ] ; then
      version_name=$(cat $cap_conf  | jq -r  .appVersion)
      version_code=$(cat $cap_conf  | jq -r  .appVersionCode)

      if [ -f "$and_conf" ] ; then
        mv $and_conf $and_conf.prev
        sed -e "s/versionCode .*/versionCode $version_code/"  \
            -e "s/versionName .*/versionName \"$version_name\"/" \
            < $and_conf.prev > $and_conf
        rm $and_conf.prev
      else 
        echo "Android: $and_conf not found, skipping"
      fi

      if [ -f "$ios_conf" ] ; then
        mv $ios_conf $ios_conf.prev
        xmlstarlet ed -u '/plist/dict/key[text() = "CFBundleShortVersionString"]/following-sibling::string[1]'  \
                   -v $version_name $ios_conf.prev > $ios_conf
        mv $ios_conf $ios_conf.prev
        xmlstarlet ed -u '/plist/dict/key[text() = "CFBundleVersion"]/following-sibling::string[1]'  \
                   -v $version_name $ios_conf.prev > $ios_conf
        rm $ios_conf.prev
      else 
        echo "iOS: $ios_conf missing, skipping"
      fi

    else 
      echo "$cap_conf not found. Giving up."
      exit
    fi
    ;;

  android-cap)
    cap_conf=capacitor.config.json
    if [ -f $cap_conf ] ; then
      app_pkg=$(cat $cap_conf  | jq -r  .appId)
      ionic cap copy
      ionicassist sync-cap
      cd android ; ./gradlew installDebug
      $ADB shell monkey -p $app_pkg 1
    else 
      echo "$cap_conf not found. Giving up";
    fi
    ;;

  ios-cap)
    ionic cap copy
    ionicassist sync-cap
    ionic cap open  ios
    ;;
  *)
    echo "Usage: $(basename $0) {grab-apk|grab-release-apk|screencap|adb|sync-cap|android-cap}"
    exit 1
    ;;
esac

No comments:

Post a Comment