自作Piece/iiideaで動作確認

目次

この章は以下の節で構成されています。

OverviewSection5

自分でアップロードしたPieceを利用してiiideaを作る

前章までで、みなさんが作成した皆さんのアプリ上だけで動作するPieceRiiiverに登録しました。まだ登録しただけですので、他の人には作成したPiece達を見ることはできませんが、皆さんは自分で登録したPieceですのでテスト利用できます。

注意: テスト利用中のPieceを利用するには、開発者登録したEmailアカウントと同じEmailRiiiverにログインする必要があります。開発者としての登録とRiiiverのユーザー登録は別です。開発者登録をしたEmailアカウントでそのままRiiiver環境に入ろうとして、登録されていないアカウントと言うエラーが出る場合、Riiiver環境にも同じEmailアドレスを利用して登録して下さい。開発者登録されているEmailアドレスとRiiiverのユーザー登録したEmailアドレスが同じ場合に限り、ユーザー側にテスト利用中Pieceが見えるようになります。

早速RiiiverAppを利用して、iiideaを作成しましょう。RiiiverAppの利用方法はこのクイックマニュアルを参照して下さい。今回のiiideaづくりの目的は皆さんが作ったT/S/A Piece全て含まれたiiideaを作ることです。以下の手順でiiideaを作ることになります。

  1. ブランドは皆さんの「企業情報」で登録した情報を選択しましょう。(最初にCITIZENと表示されている部分を横にスワイプすると次々といろいろなブランドの名称とアイコンが見えるはずです)
  2. ブランドを選択後、デバイスを選択しましょう。皆さんが登録したデバイスの名前とアイコンが表示されているはずです。
    (下の画面の水色の部分に皆さんが登録した企業アイコン、デバイスの部分に皆さんが登録したデバイスの写真が出ているはずです。写真を登録しなかった場合は、デバイス名のみが表示されます)
    RiiiverApp_MakingIiidea.jpeg
  3. T, S, Aの選択画面でみなさんが作ったPieceを選択しましょう。
    S PieceのカテゴリはサンプルアプリのPieceJSONを変更していない場合は、「Communication」のカテゴリのS Pieceとして表示されます。
  4. 作成できましたか?以下の画面になれば作成完了です。
    iiideaMakingComplete.jpeg
    ここで「ストアからダウンロード」ボタンを押して、iiideaをダウンロードして下さい。
作成したiiideaをダウンロードする

前節の最後の作業で、あなたのユーザーアカウントには、作成したiiideaがダウンロードされているはずです。

iiideaのダウンロードとは?

先程、Riiiverストアからiiideaをダウンロードしましたが、どこにダウンロードされたのでしょう?

iiideaのダウンロード

RiiiverApplyを通じて、Riiiverストアサイトにアクセスし、今回の上の例ではiiidea「さ」をダウンロードしました。上図のように、実際はRiiiverAppにダウンロードしたというよりは、Riiiverのクラウド上で管理しているユーザー情報に、このユーザーはiiidea「さ」をダウンロードしたというデータをサーバ側のデータベースに残しているだけです。ユーザーがどの様なiiideaをダウンロードしているのかは全てクラウドサイドで管理されています。同じユーザーが別のアプリを利用してiiideaを利用するときも、クラウド側からダウンロードしたiiideaのリストもらってからiiideaを利用します。今扱っているサンプルアプリも同様です。下図を見て下さい。

getMyApplet

サンプルアプリはRiiiverにログイン後に、ログインされたユーザーがダウンロードしているiiideaの一覧を取得できます。(詳しく言うと、RiiiverSDKgetMyApplet()というメソッドを利用することで、iiideaリストを取得することが出来ます。)ダウンロードされたiiideaリストを要求するアプリはDeviceIDVendorIDを指定することにより、そのユーザーがダウンロードしているiiideaの中でも、指定したDeviceVendorアプリで扱うことができるiiideaのみが見えるようになります。

それでは、実際にサンプルアプリを利用して、みなさんが作ったiiideaを取得して表示してみましょう。

サンプルアプリには、開発者アカウントと同じEmailアカウントでログインしていることを確認して下さい。もし、開発者登録したEmailアカウントとは別のアカウントでログインしている場合には、開発者登録したEmailアドレスと同じEmailアドレスでRiiiverアカウントにログインし直して下さい。

ログインすると、今まで真っ黒だった画面から、皆さんが作ったiiideaが見える下の絵のようなMy iiideaリスト画面が表示されるようになったはずです。

Login WebView
この様にユーザーからするとRiiiverUXとして、

  1. 新しいiiideaはストアからダウンロード。
  2. ダウンロードしたiiideaは対応するデバイスで表示、設定する。
    という流れに必ずなります。次のUXとしては、
  3. 対応アプリからiiideaを有効に(アクティベート)する。
  4. 対象のTrigger Pieceiiideaを実行する。

という体験になります。今回は皆さんはサンプルアプリを利用して、みなさんが作成したiiideaを実行しようとしています。実際にダウンロードしたiiideaを実行ところをデバッグして確認してみましょう。

iiideaをアクティベートする

My iiideaリスト画面に出たiiideaは利用する時にはiiideaを有効にする必要があります。これをiiideaアクティベートすると言うことにします。
サンプルアプリではOS標準提供のUIスイッチを利用して各iiideaごとに有効・無効を切り替えられる様に実装しています。
詳細についてiOS版の場合はIiideaInfoViewController.swiftfunc activateChangedの中身を、Android版の場合はIiideaInfoFragment.ktactivate.setOnCheckedChangeListenerの中身をデバッグしながら確認してみてください。

サンプルアプリでは、My iiideaリストのiiideaのリストをタップするとiiideaの詳細画面が表示され、この詳細画面でiiideaをアクティベートできる様に設計しています。

Login WebView

Activate this iiidea」のスイッチで有効無効の切り替えを行います。デバッグしながらこのスイッチを切り替えてみましょう。切り替わり時にデバッグLog

...
activate succeeded.
...
deactivate succeeded.

と切り替わることを確認できるはずです。(このログはiOS版ではRiiiverSDKManager.swiftAndroid版ではRiiiverSDKManager.ktが出力しています。)

このデバッグの確認作業で重要なことは、iiideaを利用するとき、つまりトリガーを引く前にはアクティベートしなければならないということです。

サンプルアプリの動作をデバッグする

iiideaをアクティベートした後は、いよいよiiideaを実行してみましょう。

サンプルアプリでは、アクティベートをしたiiidea詳細画面の中にExecute TriggerPieceCoreSampleというボタンを配置し、これをタップすることでiiideaを実行する様に実装しています。つまり、「Execute TriggerPieceCoreSample」ボタンを押すことが、みなさんが作成したiiideaのトリガーです。このボタンが押されるとT PiecePieceCoreが実行されます。

Execute TriggerPieceCoreSampleがタップされると、
iOS版ではIiideaInfoViewController.swift内のtriggerPieceCoreSample関数が呼ばれ、
Android版ではIiideaInfoFragment.kt内のpieceCore1.setOnClickListener関数が呼ばれます。

iOSAndroid

...
@IBAction func triggerPieceCoreSample(_ sender: Any) {
  guard let iiidea = iiidea else {
    print("trigger failed. reason: No iiidea")
    return
  }

  print("preference:\(iiidea.triggerBlockUserPref ?? [:])")

  var userInfo: [String:Any] = [:]
  userInfo[TriggerNotification.Key.Identifier] = iiidea.getId()
  userInfo[TriggerNotification.Key.Output] = triggerInput.text
  
  // iiidea実行の通知
  NotificationCenter.default.post(name: TriggerNotification.Name.sampleTrigger, object: nil, userInfo: userInfo)
}
...

...
pieceCore1.setOnClickListener {
    Timber.d("pieceCore1")
    val text = output_edit!!.text.toString()
    val intent = Intent(TriggerPieceCoreSample.ACTION)
    intent.putExtra("identifier", iiidea!!.id)
    intent.putExtra("output", text)
    activity!!.sendBroadcast(intent)
}
...

このサンプルアプリではボタンが押されたら、iiideaが実行されたことを通知センターやブロードキャスト機能を利用してアプリ内で伝える仕組みをとっています。なぜそうしているかと言いますと、一つのT Pieceが複数のiiideaから利用されるときの場合を考えているからです。

ここの部分はアクティベート、Trigger Pieceのデリゲーション、そしてPieceJSONからの設定が深く関連しているので詳細な説明をしていきます。

PieceJSONPieceCoreの関係の理解が重要です。不明点などがあればこの仕様を今一度ご覧ください。最後まで読み終わりましたら、ここに戻ってきてください。

iiidea動作時の動きを確認する

サンプルアプリの動作確認とデバッグをしていきながら、SDKが実際に何をしているのか見ていきましょう。アンプルアプリをビルドしてデバッグ状態にします。そしてアプリに以下の操作をして見ましょう。

  1. サンプルアプリを立ち上げます。
  2. サンプルiiideaをアクティベートします。
  3. Execution iiideaボタンの上にあるテキストフィールドに"Hello!!"と入力します。
  4. Execution iiideaボタンをタップします。

Execute iiidea

すると、以下のようなデバッグログが表示されるはずです。

iOSAndroid

Trigger is fired! output:["triggerSampleInfo": "Hello!!"], preference:[:]
ServicePieceCoreSample input:["sampleInfo": "Hello!!"], preference:[:]
ActionPieceCoreSample input:["sampleInfo": "Hello!!"], preference:[:]

TriggerPieceCoreSample Trigger is fired! output:{triggerSampleInfo=Hello!!}
ServicePieceCoreSample inputJson: {"sampleInfo":"Hello!!"}, preferenceJson: {}
ActionPieceCoreSample inputJson: {"sampleInfo":"Hello!!"}, preferenceJson: {}

それでは、次にそれぞれのPieceが呼ばれていく過程を見ていきましょう。このサンプルアプリではとても単純なことをやっています。

iiidea Behavior

テキストフィールドに入力した"Hello!!"という文字列がTrigger Pieceから出力され、"Hello!!"はService Piece Action Pieceの順に渡されます。iiidea実行中の仕組みは少々複雑なので、ステップごとに見ていきます。

アクティベーション

エンドユーザー目線では、スイッチをスワイプするだけでiiideaをアクティベートできますが、

iiidea Behavior

実際には、サンプルアプリ内で以下のような処理が走っています。

iiidea Behavior

  • Pieceにユーザーが設定したPreferenceを確認する(プリファレンス設定についてはこの資料をご覧ください)
  • PieceJSONPieceCoreをつなげる

この処理はiOS版ではIiideaInfoViewController.swift内のfunc activateChanged()に記述され、Android版ではIiideaInfoFragment.kt内のactivate.setOnCheckedChangeListenerに記述されています。
プリファレンスの部分については、iiideaのアクティベートには本質的に関係しないので、ここでは説明を割愛します。(この部分について詳しく知りたい方はこちらをご覧ください)

重要なのは、対象のiiideaのアクティベートをRiiiver SDKにお願いする部分の実装です。

iOSAndroid

func activate(iiidea: ERApplet, completion: @escaping ((_ isSuccess: Bool, _ error: Error?) -> Void)) {
    // セッションが切れないように毎回トークンを更新  -  Update the token to keep the auth session.
    self.getAWSToken { (isSuccess, error) in
        if (isSuccess == false) {
            print("activate failed. reason: No token")
            completion(false, error)
            return
        }
        // Activate the iiidea via Riiiver SDK.
        iiidea.activate() { unauthPermissions, serviceProxyData, error in
            guard unauthPermissions == nil || unauthPermissions == [] else {
                print("activate failed. reason:No permission")
                completion(false, error)
                return
            }
            guard error == nil else {
                let riiiverError:ERSDKError = error as! ERSDKError
                print("activate failed. reason: \(riiiverError.rawValue)")
                completion(false, error)
                return
            }
            print("activate succeeded.")
            completion(true, error)
        }
    }
}

fun activate(iiidea: ERApplet, completion: (isSuccess: Boolean, error: ERError?) -> Uni) {
    val erSdk = erSdk
    if (erSdk == null) {
        // initialize 未完了
        Timber.e("not initialized")
        completion(false, null)
        return
    }

     // セッションが切れないように毎回トークンを更新  -  Update the token to keep the auth sesion.
    getAWSToken { isSuccess: Boolean, error: Exception? ->
        if (!isSuccess) {
            Timber.e(
                "updateAWSToken failed. reason: %s",
                if (error != null) error.message else ""
            )
            completion(false, null)
            return@getAWSToken
        }

         iiidea.activate(context) { _: Array<String>?, _: Array<ERServiceProxyData>?, eError: ERError? ->
            if (erError == null) {
                Timber.d("activate succeeded.")
                completion(true, null)
            } else {
                completion(false, erError)
            }
        }
    }
}

iiideaをアクティベートしようとすると、Riiiver SDKは以下のようなタスクを実行します。

  • iiideaと、そのiiideaを構成するPieceの確認
  • PiecePieceJSONの中身を読み込み、"executor"のキーを確認
  • 確認した"executor"の値に基づいてPieceCoreを探し、その名前のクラスを読み込み

さらに詳細に動作を追っていきましょう。

iiidea Behavior

上の図は、PieceJSONPieceCoreをつなぐ処理です。もしPieceJSONによって指定されたクラスを見つけられなかったら、SDKはエラーを返します。図中のstep 4にあるように、アクティベーションに成功すると、Trigger PieceCoreのクラス内の関数createTrigger()enable()を自動的に呼び出します。

iOS版のサンプルアプリでは、各PieceCorePieceCoreフォルダ内に格納されています。
実際にアプリを開発する時は、ここに皆さんが作るファイルを置くことになります。

PieceCore location

AndroidSDKでは、予めSDKに各PieceCoreを格納しているPathを設定する必要があります。
iOS版では不要です。

PieceCore location

Android版サンプルアプリではjp.co.riiiver.sdk.sample.piececorePieceCoreを格納していますので以下のようにSDKに設定します。

iOSAndroid

iOS版ではこの処理は不要です。

object RiiiverSDKConfig {
    var pieceCorePackageName = "jp.co.riiiver.sdk.sample.piececore"
...
object RiiiverSDKManager {

    private var erSdk: ERSDK? = null

    var accessToken: String? = null
    var idToken: String? = null
    private var context: Context? = null

    /**
     *  RiiiverSDKを初期化する
     */    fun initialize(activity: Activity, complete: (isLoggedIn: Boolean) -> Unit) {
        context = activity
        ERAuth.initialize(activity, ERAuthDelegateImpl(activity)) {
            Timber.i("initialized")

            erSdk = ERSDK.Builder.with(activity)
                .includeBlockExecutorPackageName(pieceCorePackageName).build()

            complete(ERAuth.getInstance().isLoggedIn)
        }
        // Preference関連の初期化
        IiideaSettingFragment.initialize(activity)
    }
...

これからT, S, Aの各PieceCoreの動作について解説していきますが、サンプルアプリとRiiiverSDKとアプリ内のクラスとして実装するPieceCoreの連携を示した図を下に先にお見せしておきます。

iiideaExecutionTimeLine

次からの各PieceCoreの動作の説明は上の図と照らし合わせながら読んで下さい。

Trigger Pieceの発火

Trigger PieceCoreは、ERTriggerBlockExecutorクラスを継承することで作ることができます。
ERTriggerBlockExecutorクラスはcreateTrigger()enable()disable()の関数を持っていますが、これらはS PieceA PiecePieceCoreにはありません。
T Pieceiiideaを開始させることが目的なので、他のPieceCoreクラスとインターフェースが異なっています。

Trigger PieceCoreの各関数について簡単に説明します。

createTrigger()
iiideaをアクティベートするとPieceCoreで継承しているERTriggerBlockExecutorクラスのインスタンスがRiiiverSDK内部で生成されます。そのタイミングでcreateTriggerが呼ばれます。引数にiiideaIDが含まれていいるので、アクティベートされたiiideaが何なのか、T PieceCore側でも判断することができます。

T Pieceに何かユーザーが設定できる項目がある場合、ユーザーがPreference設定画面で入力した値もこの関数が呼ばれた時に引数preferenceJsonとして渡ってきます。ユーザーの設定を読み取り、処理の情報に利用することができます。

enable()
アクティベートされると呼び出されます。上記のcreateTrigger()の直後に呼ばれます。
この時にERTriggerBlockDelegate のインスタンスが渡されます。
iiideaを実行するためにはこのインスタンスを利用してトリガーを引きます。

disable()
ディアクティベートされる(iiideaが無効化される)と呼び出されます。
enable()で渡されたERTriggerBlockDelegateのインスタンスは使用できなくなります。
disable()を読んだ後は、対象のiiideaは実行することはできません。

サンプルアプリで実際にTriggerの発火を行っているTriggerPieceCoreSampleを見てみましょう。

iOSAndroid

@objc(TriggerPieceCoreSample)
class TriggerPieceCoreSample: NSObject, ERTriggerBlockExecutor {
    weak var delegate: ERTriggerBlockDelegate?
    var identifier: String?
    
    // TriggerPieceCoreSample の実行要求通知がきたら実行される
    // called when the notification of the TriggerPieceCoreSample execution comes.
    @objc private func didReceiveTriggerPieceCoreSampleTrigger(_ notification: Notification) {
        guard let userInfo = notification.userInfo else { return }
        
        // Check the ID - IDのチェック
        guard let identifier = userInfo[TriggerNotification.Key.Identifier] as? String else {
            print("TriggerPieceCoreSample error in execution. reason:no ID")
            return
        }
        if identifier != self.identifier {
            // does not fire because the ID is wrong. - IDが異なるため発火しない
            return
        }
        
        // TriggerPieceCoreSampleのアウトプット - Get the text field's string and set it as a output of the T piece.
        var output = [ "triggerSampleInfo" : "" ]
        output["triggerSampleInfo"] = userInfo[TriggerNotification.Key.Output] as? String
        print("TriggerPieceCoreSample Trigger fire! output:\(String(describing: output))")
        
        // Trigger with outpur information - output情報を設定してTrigger発火
        delegate!.didTrigger(outputJson: output)
    }
    
    func createTrigger(blockJsonString: String, preferenceJson: Dictionary<String, Any>, applet: ERApplet) -> Bool {
        print("TriggerPieceCoreSample:\(self.identifier ?? "")]")
        identifier = applet.getId()
        return true
    }
    
    func enable(delegate: ERTriggerBlockDelegate) -> Bool {
        print("TriggerPieceCoreSample enable")
        self.delegate = delegate
        
        // sampleTrigger実行通知を監視を開始する。通知を受けたら didReceiveTriggerPieceCoreSampleTrigger() を実行する
        // Starts ovserving the notification for the "sampleTrigger" execution.  When received the notification, executes didReceiveTriggerPieceCoreSampleTrigger().
        NotificationCenter.default.addObserver(self, selector: #selector(didReceiveTriggerPieceCoreSampleTrigger(_:)), name: TriggerNotification.Name.sampleTrigger, object: nil)
        return true
    }
    
    func disable() {
        print("TriggerPieceCoreSample disable")
        delegate = nil
        
        // sampleTrigger実行通知を監視を終了する。
        // Stop observing the notification for the sampleTrigger execution.
        NotificationCenter.default.removeObserver(self, name: TriggerNotification.Name.sampleTrigger, object: nil)
    }
}

class TriggerPieceCoreSample(val context: Context) : ERTriggerBlockExecutor, BroadcastReceiver() {
    var mDelegate: ERTriggerBlockDelegate? = null
    var mIdentifier: String? = null

    companion object {
        const val ACTION = "jp.co.riiiver.sdk.sample.piececore.intent.action.TriggerPieceCoreSample"
    }

    override fun onReceive(context: Context?, intent: Intent?) {
        intent?.also {
            val identifier = intent.getStringExtra("identifier")

            if (!mIdentifier.equals(identifier)) {
                // does not fire because the ID is wrong. - IDが異なるため発火しない
                return
            }

            val output = java.util.HashMap<String, Any>()
            output["triggerSampleInfo"] = intent.getStringExtra("output")

            mDelegate?.also { delegate ->
                Timber.d("TriggerPieceCoreSample Trigger fire! output:%s", output)
                delegate.didTrigger(context, output)
            } ?: run {
                Timber.e("TriggerPieceCoreSample createTrigger:delegate is null")
            }
        }
    }

    override fun createTrigger(
        blockJsonString: String?,
        preferenceJson: JSONObject?,
        applet: ERApplet?
    ) {
        Timber.d("TriggerPieceCoreSample createTrigger:%s", "")

        applet?.also {
            this.mIdentifier = applet.id
        } ?: run {
            Timber.e("TriggerPieceCoreSample createTrigger:applet is null")
        }
    }

    override fun enable(delegate: ERTriggerBlockDelegate?): Boolean {
        Timber.d("TriggerPieceCoreSample enable:%s", "")

        delegate?.also {
            this.mDelegate = delegate
        } ?: run {
            Timber.e("TriggerPieceCoreSample createTrigger:delegate is null")
        }

        // ブロードキャストIntentの監視を開始する
        context.applicationContext.registerReceiver(this, IntentFilter(ACTION))

        return true
    }

    override fun disable() {
        Timber.d("TriggerPieceCoreSample disable:%s", "")

        if (mDelegate != null) {
            mDelegate = null
            // ブロードキャストIntentの監視を終了する
            context.applicationContext.unregisterReceiver(this)
        }
    }
}

TriggerPieceCoreSampleのインスタンスが生成されたときに呼ばれるcreateTrigger()iiideaID情報を保持します。
理由は先程も「一つのT Pieceが複数のiiideaから利用されるときの場合を考えているから」と記述したところと関連するからです。後ほど説明します。

アクティベートされた時に呼ばれるenable()ERTriggerBlockDelegateのインスタンスを保持します。
サンプルアプリではiiideaを実行するボタン(Execute TriggerPieceCoreSample)をタップすると、iiideaが実行されたことを示す通知(iOSではNotificationCenterAndroidIntent)を送るように作られていますので、enable()が呼ばれたタイミングでその通知の監視を開始します。

ディアクティベートされた時に呼ばれるdisable()では保持していたERTriggerBlockDelegateのインスタンスを破棄し、iiidea実行通知の監視を終了します。

ここまでがERTriggerBlockExecutorの関数に対する実装です。

次にiiidea実行ボタンがタップされた時に呼ばれる処理を見てみましょう。

iOSAndroid

@objc private func didReceiveTriggerPieceCoreSampleTrigger(_ notification: Notfication) {
    guard let userInfo = notification.userInfo else { return }
    
    // Check the ID - IDのチェック
    guard let identifier = userInfo[TriggerNotification.Key.Identifier] as? Strng else {
        print("TriggerPieceCoreSample error in execution. reason:no ID")
        return
    }
    if identifier != self.identifier {
        // does not fire because the ID is wrong. - IDが異なるため発火しない
        return
    }
    
    // TriggerPieceCoreSampleのアウトプット - Get the text field's string and setit as a output of the T piece.
    var output = [ "triggerSampleInfo" : "" ]
    output["triggerSampleInfo"] = userInfo[TriggerNotification.Key.Output] as?String
    print("TriggerPieceCoreSample Trigger fire! output:\(String(describing: outut))")
    
    // Trigger with outpur information - output情報を設定してTrigger発火
    delegate!.didTrigger(outputJson: output)
}

override fun onReceive(context: Context?, intent: Intent?) {
    intent?.also {
        val identifier = intent.getStringExtra("identifier")

        if (!mIdentifier.equals(identifier)) {
            // does not fire because the ID is wrong. - IDが異なるため発火しない
            return
        }

        val output = java.util.HashMap<String, Any>()
        output["triggerSampleInfo"] = intent.getStringExtra("output")

        mDelegate?.also { delegate ->
            Timber.d("TriggerPieceCoreSample Trigger fire! output:%s", output)
            delegate.didTrigger(context, output)
        } ?: run {
            Timber.e("TriggerPieceCoreSample createTrigger:delegate is null")
        }
    }
}

この関数でiiidea実行ボタンがタップされたことを受けてiiideaを実行するまでの処理を行います。つまり、ここからがT PiecePieceCoreの処理が開始することになります。
受け取った通知には実行対象のiiideaIDServicePieceに渡すoutput情報が含まれています。

まずcreateTrigger()が呼ばれた際に保持していたiiideaIDと実行対象のiiideaIDを比較チェックしています。ここが一つのT Pieceが複数のiiideaから利用されることを考慮している部分です。先に示した図の通り、T Pieceも色々なiiideaのきっかけになる場合があります。例えば、「ボタンを押すとトリガー」と言うT Pieceを作った場合、ボタンを押したら天気予報を取ってくる、知り合いにメールを送る、など同じT Pieceで複数のiiideaを実行する場合が考えられます。これらのiiideaが同時に複数アクティベートされているとすると、同じT Pieceのインスタンスが複数存在することになります。(ERTriggerBlockExecutorを継承したクラスのインスタンス)
このサンプルアプリでは、OSのブロードキャストや通知センターを利用して、例で言う全てのボタン押しT Pieceのインスタンスに対して、イベントを発行しています。それぞれのインスタンスはiiideaIDを受け取り、自分が正しいインスタンスかどうかを判断するように実装しています。

次に通知情報からServicePieceに渡すoutput情報を取得して、渡すためのフォーマットに合わせて整形しています。
ここの情報がServicePieceinput情報として渡されていくことになります。

必要であれば、この辺りでPreference設定の情報を読み取るような処理を入れることになりますが、このサンプルアプリ・サンプルT PieceではPreference設定できるPieceに設計していませんので、処理を入れていません。

T PiecePieceCoreの処理が終わったら最後にdidTriggeroutputを設定して呼び出して下さい。didTriggerを呼ぶことでRiiiverSDKはトリガーが発火され、T PieceCoreの処理が全て終わったと判断し、S Pieceの実行に移ります。

Service Pieceの実行

Service PieceCoreは、ERServiceBlockExecutorクラスを継承することで作ることができます。
ERServiceBlockExecutorクラスはexecute()関数を持っています。

Service PieceCoreの関数について簡単に説明します。

execute()
Trigger PiecedidTriggerをきっかけに呼び出されます。
Trigger Pieceoutput情報がinputJsonというパラメータに含まれています。
また、Pieceに対してユーザーがPreference設定画面で設定した情報はpreferenceJsonというパラメータに含まれています。

サンプルアプリで実際にServiceの処理を行っているServicePieceCoreSampleを見てみましょう。

iOSAndroid

@objc(ServicePieceCoreSample)
class ServicePieceCoreSample: NSObject, ERServiceBlockExecutor {
    func execute(appletId: String, blockJsonString: String, preferenceJson: Dictionary<String, Any>?, inputJson: Dictionary<String, Any>?, completionHandler: @escaping ((Dictionary<String, Any>?, ERSDKError?) -> Void)) {
        print("ServicePieceCoreSample input:\(inputJson ?? [:]), preference:\(preferenceJson ?? [:])")

        var outputJson: Dictionary<String, Any>  = [:]
        if let inputJson = inputJson {
            // inputとして受け取った情報をそのまま次のPieceへ渡す
            // Input value goes through to the next piece as an output as it is
            outputJson["serviceSampleInfo"] = inputJson["sampleInfo"]
        }

        completionHandler(outputJson, nil)
    }
}

class ServicePieceCoreSample(val context: Context) : ERServiceBlockExecutor {

    override fun execute(
        appletId: String?,
        blockJsonString: String?,
        preferenceJson: JSONObject?,
        inputJson: JSONObject?,
        completionHandler: Completion1<MutableMap<String, Any>>?
    ) {
        Timber.d("ServicePieceCoreSample inputJson: $inputJson, preferenceJson: $preferenceJson")

        inputJson?.also {
            // iunputとして受け取った情報をそのまま次のPieceへ渡す
            //Input value goes through to the next piece as an output as it is
            val outputJson = mutableMapOf(Pair("serviceSampleInfo", inputJson.get("sampleInfo")))

            completionHandler?.also {
                completionHandler.accept(outputJson, null)
            }
        }
    }
}

実際のアプリにService Pieceを組み込む際は、目的に合わせてexecute()内を書き換えてください。
ERServiceBlockExecutorを継承してさえいれば、クラス名や格納場所の変更はもちろんしていただいて構いません。
Trigger Pieceから受け取ったinput情報やユーザーが設定したpreferences情報を使ってコードを作りましょう。
必要な処理が終わったらAction Pieceに渡すoutput情報を設定してcompletionHandlerを呼ぶのを忘れずに。

completionHandler()

completionHandlerを呼ぶとRiiiver SDKS PiecePieceCoreでの処理が終了したこと報告することになります。RiiiverSDKは続けてAction Pieceを実行してくれます。

S PiecePieceCoreRiiiverSDKに内蔵されている場合

対象となるiiideaに使われているService Pieceがアプリ側に存在しない場合があります。(例えば気象情報を取得するWebサービスを利用するPieceが相当します。ウェブサービスを活用してPieceを簡単に作れるように、RiiiverではRiiiverSDKを利用せずAWS Lambdaを活用することで、S PieceA Pieceを作る仕組みも準備しています。興味がある方はこちらをご参照下さい。)この仕組みを利用する場合、S PiecePieceCoreRiiiver SDKの内部にありますので、皆さんは何も実装する必要はありませんし、動作について気にする必要もありません。RiiiverSDK内部のPieceCoreの処理が終わった後、次のA Pieceが皆さんのものだった場合には、S Pieceの終了後に皆さんのA PieceRiiiverSDKから呼ばれます。

Action Pieceの実行

Action PieceCoreは、ERActionBlockExecutorクラスを継承することで作ることができます。
ERActionBlockExecutorクラスはexecute()関数を持っています。

Action PieceCoreの各関数について簡単に説明します。

execute()
Service PiececompletionHandlerをきっかけに呼び出されます。
Service Pieceoutput情報がinputJsonというパラメータに含まれています。
また、Pieceに対してユーザーが設定した情報はpreferenceJsonというパラメータに含まれています。

サンプルアプリで実際にServiceの処理を行っているActionPieceCoreSampleを見てみましょう。
Action PieceCoreの振る舞いはService PieceCoreとそっくりです。

iOSAndroid

@objc(ActionPieceCoreSample)
class ActionPieceCoreSample: NSObject, ERActionBlockExecutor {
    func execute(appletId: String, blockJsonString: String, preferenceJson: Dictionary<String, Any>?, inputJson: Dictionary<String, Any>?, completionHandler: @escaping ((Dictionary<String, Any>?, ERSDKError?) -> Void)) {
        print("ActionPieceCoreSample input:\(inputJson ?? [:]), preference:\(preferenceJson ?? [:])")

        var outputJson: Dictionary<String, Any>  = [:]
        if let inputJson = inputJson {
            // iunputとして受け取った情報をそのまま次のPieceへ渡す
            //Input value goes through to the next piece as an output as it is
            outputJson["actionSampleInfo"] = inputJson["sampleInfo"]
        }

        completionHandler(outputJson, nil)
    }
}

class ActionPieceCoreSample(val context: Context) : ERActionBlockExecutor {

    override fun execute(
        appletId: String?,
        blockJsonString: String?,
        preferenceJson: JSONObject?,
        inputJson: JSONObject?,
        completionHandler: Completion1<MutableMap<String, Any>>?
    ) {
        Timber.d("ActionPieceCoreSample inputJson: $inputJson, preferenceJson: $preferenceJson")
        inputJson?.also {

            val outputJson = mutableMapOf(Pair("actionSampleInfo", inputJson.get("sampleInfo")))

            completionHandler?.also {
                completionHandler.accept(outputJson, null)
            }
        }
    }
}

 

Service Pieceと同様に、実際のアプリにAction Pieceを組み込む際は、目的に合わせてexecute()内を書き換えてください。ERActionBlockExecutorを継承してさえいれば、クラス名や格納場所の変更もOKです。
Service Pieceから受け取ったinput情報やユーザーが設定したpreferences情報を使ってコードを作りましょう。
必要な処理が終わったらAction Pieceに渡すoutput情報を設定してcompletionHandlerを呼ぶのを忘れずに。
completionHandlerを呼ぶとRiiiver SDKiiideaの実行を終了します。

completionHandler()

S PiececompletionHandlerと同じです。A PieceでもPieceCoreの処理が終了したらこの関数を読んで、RiiiverSDKに処理が終了したことを報告しましょう。

A Pieceは最後なので、このハンドラが呼ばれるとiiideaの実行が終了することになります。

A PiecePieceCoreRiiiverSDKに内蔵されている場合

[S PiecePieceCoreRiiiverSDKに内蔵されている場合](#S PiecePieceCoreRiiiverSDKに内蔵されている場合)と全く同じです。RiiiverSDK内部のA PieceCoreの実行終了でも、みなさんが作ったPieceCoreの実行が終了しても、RiiiverSDKとしては、iiideaの実行が終了したことになります。

RiiiverSDKではiiideaは同時に一つしか実行することが出来ません。A Pieceが終了し、iiideaの実行が終了したら、RiiiverSDKは次のPieceの実行を受け付けるようになります。

アクティベートは複数のiiideaに対して同時にすることが出来ます。

まとめと次のステップ

ここまでで、サンプルアプリのためにみなさんが作ったT PieceS PieceA Piece、これらを利用したiiideaを利用した簡単なデバッグについての説明は終了です。お疲れさまでした。

サンプルアプリを利用した説明はここまでですが、次のステップとして、みなさんのアプリにRiiiverSDKを取り入れるときの方法や注意点ついて、こちらのドキュメントで説明します。

今回のサンプルアプリではRiiiverSDKに含まれていないいくつかのファイルなどをこちらで準備した状態で配布していますで、説明ができていない部分があります。例えば、みなさんが最初に目にしたFacebookGoogleアカウントでのログイン機能についてなどです。

皆さんのアプリに取り入れる時に考えなければならない追加のUIについてなども触れていますので、ぜひご一読下さい。

用語

新名称と旧名称

旧名称新名称説明
ERRiiiverRiiiver開発時のプロジェクト名称が-ER。当時の開発したコード内には名前が残っています。適宜読み替えてください。
AppletiiideaユーザーがRiiiver Appを利用することでNo Codeで作成できる小さいアプリを指す。
BlockPieceiiideaを構成する要素。Trigger PieceServie PieceAction Pieceがある。

この記事は役に立ちましたか?