Garmin アプリ開発(5) クラス構造

apps.garmin.com

公式のサンプル

おおよそこういうクラス構造でした。上に並んでいるSDKのクラスを派生するSampleXXX という名前の4つのクラスで出来ています。

Menuが要らなければ、最小3つのクラスでアプリが作れます。

f:id:mnobori:20211122190455p:plain

sample
公式サンプルの課題

作りたいアルゴリズム(いわゆるドメイン知識とかビジネスロジック)、主に「SampleView」に入り込んでいる構造です。簡単なうちはこれでもいいですが、少し複雑なものは別のクラスに出した方がいいです。

 

自作アプリ

ということで、2つクラスを足して5個のクラスでアプリを作ります。

f:id:mnobori:20211228144815p:plain

class diagram

AdditionalTimeStopwathApp  アプリケーション本体、全体を生成する

AdditionalTimeDelegate   キー入力

AdditionalTimeView     画面表示

これらの3つのクラスはサンプルと同じGarminSDKの派生クラスになってます。この派生クラスはテストしにくいので、なるべくシンプルに保つように作っていきます

AdditionalTimeHandler    自作の入力と表示をハンドリングするクラス

StopWatch         ストップウォッチクラス

真ん中下段にあるこの2つのクラスを足します。自分が作りたい複雑なロジックはここに入れていくことにします。

 

AdditionalTimeStopwathApp

SDKのAppBaseの派生クラスで、アプリが起動したときに最初に実行されます。source code

公式: https://developer.garmin.com/connect-iq/core-topics/the-application-object/

詳細なライフサイクルは公式の解説を参照するとして、ここから自分のクラスを生成する必要があります。

    function getInitialView() as Array<Views or InputDelegates>? {
        var view = new AdditionalTimeStopwatchView();
        var handler = new AdditionalTimeHandler(view); // 追加
      var delegate = new AdditionalTimeDelegate(handler);
        return [ view, delegate ] as Array<Views or InputDelegates>;
    }

ここでは、自作クラスAdditionalTimeHandlerの生成を追加し、依存関係にあるクラスをDependency Injection(依存関係の外部からの注入)の考え方で、引数で与えています。

AdditionalTimeHandler(view);  ←HandlerはViewに依存している

AdditionalTimeDelegate(handler): ←Delegateはhandlerに依存している

 

AddtionalTimeView

レイアウトされたUIパーツに文字列を設定したり、表示を切り替えたりするクラスです。source code

 

公式: https://developer.garmin.com/connect-iq/core-topics/user-interface/

詳細なViewクラスのライフサイクルは公式参照ですが、主なものはこちらです

View.onShow(): 表示されたとき

View.onLayout(dc): レイアウトがロードされたとき

View.onUpdate(): 表示の更新

View.onHide(): 表示が終わった時

アプリの表示が終わるなどの重要イベントに関してはアプリ全体の動きが変わるのでAddtionalTimeHandlerに伝える必要があります。

そのためHandler → View間でcallbackの仕組みで、依存関係の逆転(DIP: dependency inversion principle)を行ってます

 

Handler側

    //! Constructor
    public function initialize(view as AdditionalTimeView) {
        self._view = view;
        self._stopwatch = new StopWatch();
        self._viewtimer = new Timer.Timer();
        view.Register(method(:onShow), method(:onHide)); // ← コールバック登録
        self._lastvibrated = System.getTimer();
    }

View側

//コールバック呼び出し
   function onHide() as Void {
        if(self._onHideCallback){
            self._onHideCallback.invoke();
        }
  }
//コールバック登録
    public function Register(onShowCallback as Method, onHideCallback as Method){
        self._onShowCallback = onShowCallback;
        self._onHideCallback = onHideCallback;
    }

これで依存関係とは反対方向の呼び出しが可能になります

 

AddtionalTimeDelegate

キー入力をハンドリングするクラスです source code

公式: https://developer.garmin.com/connect-iq/core-topics/input-handling/

onKey()

onKeyPressed()

onKeyReleased()

このアプリでは上記3つだけハンドリングしています。

Pressed/Releasedはボタン長押し判定が必要な場合にハンドリングします