Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SDK 개발자로 살아남는 법

JoonHo Park
February 02, 2018

SDK 개발자로 살아남는 법

앱을 만드는 것 보다, 여러 앱에 들어갈 수 있는 SDK를 만들고 싶어서 입사했다. 그런데 뭔가 이상하다. 그동안 쓰던건 못 쓰고, 안하던 일은 해야하고, 아무튼 안드로이드 스튜디오는 똑같이 쓰고 있는데 뭔가 다르다. SDK 개발을 하면서 앱 개발과 다르게 어려웠던 일들과 이를 어떻게 헤쳐나갔는지 공유해 봅니다.

JoonHo Park

February 02, 2018
Tweet

More Decks by JoonHo Park

Other Decks in Technology

Transcript

  1. ੉࢚ • ੗ਬ܂ѱ ѐߊೞח ٣૑ణ ֢݃٘ • ୭न ӝࣿ੄ ੸ӓ੸ੋ

    بੑ • ӝࣿ ࠶۽Ӓ৬ ೣԋೞח #ࣗా • ૘઺ೡ ࣻ ੓ب۾ ࣻ۴ػ ݃ੌझహ • ੗زചػ ࠽٘ / పझ౟ / ߓನ • ౱ղ ѐߊ੗р੄ ഝߊೠ ܻ࠭ • ղо ׮ ೧ࠄ׮
  2. അप • ୹ృӔী ঵ݒੋ ҵੋ न࠙ • ۨѢद ௏٘৬ ޅ

    ॳח ۄ੉࠳۞ܻ • ࠗܰݶ оঠೞח ӝࣿ૑ਗ • बबೞݶ ஖Ҋ ٜযয়ח ਃҳࢎ೦ • ֢ز ૘ড੸ੋ ࠽٘ / పझ౟ / ߓನ • ഒ੗݅੄ Ҋةೠ উ٘۽੉٘ ࣛ೒ • ղо ׮ ೠ׮
  3. • ୹ృӔী ঵ݒੋ ҵੋ न࠙ • ܻಂష݂ ߂ ੄ઓࢿ ѐࢶ

    • API ޙࢲച৬ ௏٘ о੉٘ ѐࢶ • ౱ ழޭפா੉࣌ ѐࢶ • ࠽٘ / పझ౟ ѐࢶ ߂ ੗زച • ѐߊ౱ ݽفо ೣԋೞח ܻ࠭ޙച • ղо ׮ ೠ׮, ઑӘ ؊ ಞೞѱ ރࠗܿ
  4. ౱ ழޭפா੉࣌ ѐࢶ ౱ ழޭפா੉࣌ਸ ѐࢶ೧ঠ ೞח ੉ਬ • ࠁా

    ղо ೞӝ फ਷Ѥ թب ೞӝ फ׮. • ౱ীࢲ ࠛಞೠ ࠗ࠙ਸ ೧Ѿೞݶ ౱ਗ੄ ࣻ ݅ఀ ੉ٙਸ ࠄ׮. • ੋ૑ೞ૑ ޅ ೞח о਍ؘ ߈ࠂغח दрਸ ઴੉ݶ ೡ ੌী ૘઺ೡ ࣻ ੓׮. • ࠛಞઙ੗о աۄࢲ ੉Ѫ੷Ѫ ٜ०दѱ ؽ • рױೠ ݾ಴ : “؀୽ ੓ਸ Ѫ эইࢲ ٜযщ؊פ ੓؊ۄ”
  5. য૵׮ࠁפ ҙܻ੗ Slack : GitHub / Zapier / WebHook Integrations

    / Channel Order Asana : Custom Property / Team Color / Weekly Project Confluence : GitHub Integration / Plugins GitHub : GitHub Organization / Protected Branch / Git-Flow Tech Blog : GitHub Page + Jekyll ౱ ழޭפா੉࣌ ѐࢶ
  6. Hello, Gradle! “Aী׮о ੉۠ ӝמ ػ׮Ҋ ফӝ೮חؘ” “Bীࢲח ੉۠ ӝמ

    ֍য׳ۄחؘ" “Cی ೞ۰ݶ ੉۠ ӝמ਷ ֍য઻ঠѷחؘ” “ࢲߡীࢲ ӝמ ୶о೮חؘ పझ౟೧ࠊঠ ೡ Ѫ э਷ؘ" “௏যীࢲ ӝמ ୶о೮חؘ प ӝӝ పझ౟о ೙ਃೡ Ѫ э਷ؘ” “঒ ৘੹ী ݅ٚ పझ౟ জ લ঻חؘ, ঱ઁ ਃ୒ೠ জੋ૑ ݽܰѷ֎” “పझ౟ ਊਵ۽ח ѐߊࢲߡ ࠢৈঠ ೞחؘ” “ߓನೡ ٸח पࢲߡ ࠢৈঠ ೞחؘ” “ߓನೡ ٸח դةചب ೞҊ ۽Ӓب ࡐঠೞחؘ” “…”
  7. Hello, Gradle! “Aী׮о ੉۠ ӝמ ػ׮Ҋ ফӝ೮חؘ” “Bীࢲח ੉۠ ӝמ

    ֍য׳ۄחؘ" “Cی ೞ۰ݶ ੉۠ ӝמ਷ ֍য઻ঠѷחؘ” “ࢲߡীࢲ ӝמ ୶о೮חؘ పझ౟೧ࠊঠ ೡ Ѫ э਷ؘ" “௏যীࢲ ӝמ ୶о೮חؘ प ӝӝ పझ౟о ೙ਃೡ Ѫ э਷ؘ” “঒ ৘੹ী ݅ٚ పझ౟ জ લ঻חؘ, ঱ઁ ਃ୒ೠ জੋ૑ ݽܰѷ֎” “పझ౟ ਊਵ۽ח ѐߊࢲߡ ࠢৈঠ ೞחؘ” “ߓನೡ ٸח पࢲߡ ࠢৈঠ ೞחؘ” “ߓನೡ ٸח դةചب ೞҊ ۽Ӓب ࡐঠೞחؘ” “…” Version Tag
  8. Hello, Gradle! “Aী׮о ੉۠ ӝמ ػ׮Ҋ ফӝ೮חؘ” “Bীࢲח ੉۠ ӝמ

    ֍য׳ۄחؘ" “Cی ೞ۰ݶ ੉۠ ӝמ਷ ֍য઻ঠѷחؘ” “ࢲߡীࢲ ӝמ ୶о೮חؘ పझ౟೧ࠊঠ ೡ Ѫ э਷ؘ" “௏যীࢲ ӝמ ୶о೮חؘ प ӝӝ పझ౟о ೙ਃೡ Ѫ э਷ؘ” “঒ ৘੹ী ݅ٚ పझ౟ জ લ঻חؘ, ঱ઁ ਃ୒ೠ জੋ૑ ݽܰѷ֎” “పझ౟ ਊਵ۽ח ѐߊࢲߡ ࠢৈঠ ೞחؘ” “ߓನೡ ٸח पࢲߡ ࠢৈঠ ೞחؘ” “ߓನೡ ٸח դةചب ೞҊ ۽Ӓب ࡐঠೞחؘ” “…” Product Flavor
  9. Hello, Gradle! “Aী׮о ੉۠ ӝמ ػ׮Ҋ ফӝ೮חؘ” “Bীࢲח ੉۠ ӝמ

    ֍য׳ۄחؘ" “Cی ೞ۰ݶ ੉۠ ӝמ਷ ֍য઻ঠѷחؘ” “ࢲߡীࢲ ӝמ ୶о೮חؘ పझ౟೧ࠊঠ ೡ Ѫ э਷ؘ" “௏যীࢲ ӝמ ୶о೮חؘ प ӝӝ పझ౟о ೙ਃೡ Ѫ э਷ؘ” “঒ ৘੹ী ݅ٚ పझ౟ জ લ঻חؘ, ঱ઁ ਃ୒ೠ জੋ૑ ݽܰѷ֎” “పझ౟ ਊਵ۽ח ѐߊࢲߡ ࠢৈঠ ೞחؘ” “ߓನೡ ٸח पࢲߡ ࠢৈঠ ೞחؘ” “ߓನೡ ٸח դةചب ೞҊ ۽Ӓب ࡐঠೞחؘ” “…” BuildType
  10. Hello, Gradle! static String computeVersionName() { String tag = "git

    describe --tags --abbrev=0 --exact-match".execute().text.trim() String commit = "git rev-parse HEAD".execute().text.trim().substring(0, 7) if (tag?.trim()) return tag else return commit }
  11. Hello, Gradle! def (STRING, APP_NAME, APP_KEY) = ["string", "app_name", "app_key"]

    flavorDimensions "bitsound" productFlavors { master { dimension "bitsound" applicationIdSuffix ".master" resValue STRING, APP_NAME, "Master ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } slave { dimension "bitsound" applicationIdSuffix ".slave" resValue STRING, APP_NAME, "Slave ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } develop { dimension "bitsound" applicationIdSuffix ".develop" resValue STRING, APP_NAME, "Develop ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" } publish { dimension "bitsound" resValue STRING, APP_NAME, "Publish ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" } } <resources> <!-- Gradle Controlled : DO NOT OVERRIDE --> <!--<string name="app_name"></string>--> <!--<string name="app_key"></string>--> </resources>
  12. Hello, Gradle! def (STRING, APP_NAME, APP_KEY) = ["string", "app_name", "app_key"]

    flavorDimensions "bitsound" productFlavors { master { dimension "bitsound" applicationIdSuffix ".master" resValue STRING, APP_NAME, "Master ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } slave { dimension "bitsound" applicationIdSuffix ".slave" resValue STRING, APP_NAME, "Slave ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } develop { dimension "bitsound" applicationIdSuffix ".develop" resValue STRING, APP_NAME, "Develop ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" } publish { dimension "bitsound" resValue STRING, APP_NAME, "Publish ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" } } <resources> <!-- Gradle Controlled : DO NOT OVERRIDE --> <!--<string name="app_name"></string>--> <!--<string name="app_key"></string>--> </resources>
  13. Hello, Gradle! def (STRING, APP_NAME, APP_KEY) = ["string", "app_name", "app_key"]

    flavorDimensions "bitsound" productFlavors { master { dimension "bitsound" applicationIdSuffix ".master" resValue STRING, APP_NAME, "Master ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } slave { dimension "bitsound" applicationIdSuffix ".slave" resValue STRING, APP_NAME, "Slave ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } develop { dimension "bitsound" applicationIdSuffix ".develop" resValue STRING, APP_NAME, "Develop ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" } publish { dimension "bitsound" resValue STRING, APP_NAME, "Publish ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" } }
  14. Hello, Gradle! def (STRING, APP_NAME, APP_KEY) = ["string", "app_name", "app_key"]

    flavorDimensions "bitsound" productFlavors { master { dimension "bitsound" applicationIdSuffix ".master" resValue STRING, APP_NAME, "Master ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } slave { dimension "bitsound" applicationIdSuffix ".slave" resValue STRING, APP_NAME, "Slave ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } develop { dimension "bitsound" applicationIdSuffix ".develop" resValue STRING, APP_NAME, "Develop ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" } publish { dimension "bitsound" resValue STRING, APP_NAME, "Publish ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" } }
  15. Hello, Gradle! buildTypes { debug { minifyEnabled false buildConfigField BOOLEAN,

    LOGGING, TRUE } production { initWith(buildTypes.release) minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), '../ proguard-rules-sdk.pro' buildConfigField BOOLEAN, LOGGING, FALSE } release { minifyEnabled false buildConfigField BOOLEAN, LOGGING, TRUE } }
  16. Hello, Gradle! applicationVariants.all { variant -> variant.outputs.all { output ->

    if (output != null && output.outputFileName.endsWith('.apk')) { def outputDir = "../../../../../../release-${computeVersionName()}/apk/${archivesBaseName}" def outputName = "${archivesBaseName}-${variant.name}-${computeVersionName()}.apk" output.outputFileName = "${outputDir}/${outputName}" } } } libraryVariants.all { variant -> variant.outputs.all { output -> if (output != null && output.outputFileName.endsWith('.aar')) { def outputDir = "../../../../release-${computeVersionName()}/aar/${variant.flavorName}" def outputName = "io.bitsound${variant.buildType.name == "production"? "" : "-$ {variant.buildType.name}"}-${computeVersionName()}.aar" output.outputFileName = "${outputDir}/${outputName}" } } } task prepare() { subprojects { project -> if (project.name.startsWith("bitsound")) { new File("${project.buildDir}/outputs/aar").mkdirs() } } }
  17. Hello, Gradle! release-v4.0.0/ !"" aar # !"" develop # #

    !"" io.bitsound-debug-v4.0.0.aar # # !"" io.bitsound-release-v4.0.0.aar # # %"" io.bitsound-v4.0.0.aar # %"" publish # !"" io.bitsound-debug-v4.0.0.aar # !"" io.bitsound-release-v4.0.0.aar # %"" io.bitsound-v4.0.0.aar %"" apk !"" apitestapp # !"" apitestapp-developDebug-v4.0.0.apk # !"" apitestapp-developProduction-v4.0.0.apk # !"" apitestapp-developRelease-v4.0.0.apk # !"" apitestapp-masterDebug-v4.0.0.apk # !"" apitestapp-masterProduction-v4.0.0.apk # !"" apitestapp-masterRelease-v4.0.0.apk # !"" apitestapp-publishDebug-v4.0.0.apk # !"" apitestapp-publishProduction-v4.0.0.apk # !"" apitestapp-publishRelease-v4.0.0.apk # !"" apitestapp-slaveDebug-v4.0.0.apk # !"" apitestapp-slaveProduction-v4.0.0.apk # %"" apitestapp-slaveRelease-v4.0.0.apk %"" usertestapp !"" usertestapp-developDebug-v4.0.0.apk !"" usertestapp-developProduction-v4.0.0.apk %"" usertestapp-developRelease-v4.0.0.apk
  18. పझ౟ উ੿ࢿ పझ౟ܳ ਤ೧ ࢎਊೡ ࣻ ੓ח пઙ ߑߨٜ •

    Java à JUnit • Android à Espresso / UI Automator / … • Mass Coverage à Firebase Test Lab / AWS Device Farm / … • Utility à Stetho / Firebase Remote Config / Firebase Cloud Messaging / … • Crash Reports à Fabric Crashlytics / Firebase Crash Reports / …
  19. పझ౟ উ੿ࢿ పझ౟ܳ ਤ೧ ࢎਊೡ ࣻ ੓ח пઙ ߑߨٜ •

    Java à JUnit : ҅࢑ਊ ঌҊ્ܻ Ѩૐী ਊ੉ೣ • Android à Espresso / UI Automator / … • Mass Coverage à Firebase Test Lab / AWS Device Farm / … • Utility à Stetho / Firebase Remote Config / Firebase Cloud Messaging / … • Crash Reports à Fabric Crashlytics / Firebase Crash Reports / …
  20. పझ౟ উ੿ࢿ పझ౟ܳ ਤ೧ ࢎਊೡ ࣻ ੓ח пઙ ߑߨٜ •

    Java à JUnit : ҅࢑ਊ ঌҊ્ܻ Ѩૐী ਊ੉ೣ • Android à Espresso / UI Automator / … • Mass Coverage à Firebase Test Lab / AWS Device Farm / … • Utility à Stetho / Firebase Remote Config / Firebase Cloud Messaging / … • Crash Reports à Fabric Crashlytics / Firebase Crash Reports / …
  21. పझ౟ উ੿ࢿ పझ౟ܳ ਤ೧ ࢎਊೡ ࣻ ੓ח пઙ ߑߨٜ •

    Java à JUnit : ҅࢑ਊ ঌҊ્ܻ Ѩૐী ਊ੉ೣ • Android à Espresso / UI Automator / … • Mass Coverage à Firebase Test Lab / AWS Device Farm / … • Utility à Stetho / Firebase Remote Config / Firebase Cloud Messaging / … • Crash Reports à Fabric Crashlytics / Firebase Crash Reports / …
  22. పझ౟ ೞ٘ਝয ݃੉௼ܳ ࢎਊೠ SDK పझ౟ • ীޯۨ੉ఠ ࢚ীࢲ ݃੉௼

    ࢎਊ ࠛо • ࢎਊ੉ оמೞ؊ۄب पઁ ݃੉௼ܳ ੉ਊ೧ ٛחѱ ইפۄ ৻ࠗੑ۱ ഋక • ݃੉௼ ೞ٘ਝয੄ पઁ ࢿמ਷ ӝӝ݃׮ ׮ ׮ܴ • ӝఋ पઁ ജ҃ীࢲ ߊࢤೞח ׮নೠ ੉ग৬ ৘ஏ ࠛоמೠ ߸ࣻ • ѾҴ ૒੽ ೡ ࣻ߆ী হ਺
  23. పझ౟ ೞ٘ਝয ݃੉௼ܳ ࢎਊೠ SDK పझ౟ • ীޯۨ੉ఠ ࢚ীࢲ ݃੉௼

    ࢎਊ ࠛо • ࢎਊ੉ оמೞ؊ۄب पઁ ݃੉௼ܳ ੉ਊ೧ ٛחѱ ইפۄ ৻ࠗੑ۱ ഋక • ݃੉௼ ೞ٘ਝয੄ पઁ ࢿמ਷ ӝӝ݃׮ ׮ ׮ܴ • ӝఋ पઁ ജ҃ীࢲ ߊࢤೞח ׮নೠ ੉ग৬ ৘ஏ ࠛоמೠ ߸ࣻ • ѾҴ ૒੽ ೡ ࣻ߆ী হ਺ 1. ӝࠄ పझ౟জ + ElasticSearch + Kibana (3~4ݺ 4दр ੉࢚)
  24. పझ౟ ೞ٘ਝয ݃੉௼ܳ ࢎਊೠ SDK పझ౟ • ীޯۨ੉ఠ ࢚ীࢲ ݃੉௼

    ࢎਊ ࠛо • ࢎਊ੉ оמೞ؊ۄب पઁ ݃੉௼ܳ ੉ਊ೧ ٛחѱ ইפۄ ৻ࠗੑ۱ ഋక • ݃੉௼ ೞ٘ਝয੄ पઁ ࢿמ਷ ӝӝ݃׮ ׮ ׮ܴ • ӝఋ पઁ ജ҃ীࢲ ߊࢤೞח ׮নೠ ੉ग৬ ৘ஏ ࠛоמೠ ߸ࣻ • ѾҴ ૒੽ ೡ ࣻ߆ী হ਺ 1. ӝࠄ పझ౟জ + ElasticSearch + Kibana (3~4ݺ 4दр ੉࢚) 2. Kotlin + RxJava2 + Firebase Remote Config ࢎਊ (2~3ݺ 2दр ੉࢚)
  25. పझ౟ ೞ٘ਝয ݃੉௼ܳ ࢎਊೠ SDK పझ౟ • ীޯۨ੉ఠ ࢚ীࢲ ݃੉௼

    ࢎਊ ࠛо • ࢎਊ੉ оמೞ؊ۄب पઁ ݃੉௼ܳ ੉ਊ೧ ٛחѱ ইפۄ ৻ࠗੑ۱ ഋక • ݃੉௼ ೞ٘ਝয੄ पઁ ࢿמ਷ ӝӝ݃׮ ׮ ׮ܴ • ӝఋ पઁ ജ҃ীࢲ ߊࢤೞח ׮নೠ ੉ग৬ ৘ஏ ࠛоמೠ ߸ࣻ • ѾҴ ૒੽ ೡ ࣻ߆ী হ਺ 1. ӝࠄ పझ౟জ + ElasticSearch + Kibana (3~4ݺ 4दр ੉࢚) 2. Kotlin + RxJava2 + Firebase Remote Config ࢎਊ (2~3ݺ 2दр ੉࢚) 3. FCM ੉ਊೠ Master-Slave పझ౟জ (1~2ݺ 1दр о۝)
  26. Master - Slave Test Application Master - Slave Test Application

    • ӝઓ API పझ౟জ੄ ӝמ ৮੹ ਬ૑ • Product Flavorܳ ੉ਊೞৈ Master, Slave ӝמ ઱ੑ • Slave • জ द੘द FirebaseTokenਸ ࢲߡী ١۾ • FCMਵ۽ View IDܳ ߉ਵݶ ௿ܼ प೯ • Master • ߡౡ ௿ܼ द ࢲߡ۽ View ID ੹࣠ • ࢲߡח ١۾ػ ݽٚ Slave FirebaseTokenਵ۽ View ID ੹࣠ • Relay Server • ੿݈ рױೠ View ID ઺҅ӝ • Django 2.0 + Django Rest Framework on AWS
  27. Master - Slave Test Application flavorDimensions "bitsound" productFlavors { master

    { dimension "bitsound" applicationIdSuffix ".master" buildConfigField BOOLEAN, MASTER, TRUE buildConfigField BOOLEAN, SLAVE, FALSE resValue STRING, APP_NAME, "Master ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } slave { dimension "bitsound" applicationIdSuffix ".slave" buildConfigField BOOLEAN, MASTER, FALSE buildConfigField BOOLEAN, SLAVE, TRUE resValue STRING, APP_NAME, "Slave ${computeVersionName()}" resValue STRING, APP_KEY, "01234567-89ab-cdef-0123-456789abcdef" matchingFallbacks = ["develop"] } }
  28. class FirebaseInstanceIDService : FirebaseInstanceIdService() { private val api by lazy

    { Api.requests(ApiService::class.java) } override fun onTokenRefresh() { FirebaseInstanceId.getInstance()?.token?.let { token -> Storage.put(token to Storage.FIREBASE_TOKEN) try { Observable.just(this.applicationContext) .map<Optional<String>>({ ctx -> GoogleApiAvailability.getInstance()?.isGooglePlayServicesAvailable(ctx) .takeIf { it == ConnectionResult.SUCCESS } .let { try { optionalOf(AdvertisingIdClient.getAdvertisingIdInfo(ctx)?.id) } catch (e: Throwable) { Timber.e(e) emptyOptional() } } }) .filter { it.exists } .map { Storage.put(it.value to Storage.ADID) } .filter { BuildConfig.SLAVE } .flatMap { api.register(it as String, token, Build.MODEL)} .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ Timber.d(if (it.success) "Success" else "Failure") }, { Timber.e(it) }) } catch (e: Exception) { Timber.e(e) } } } } Master - Slave Test Application
  29. object FirebaseCloudMessageForSlave { var id: Int = 0 set(value) {

    field = value publisher.onNext(field) } val publisher: PublishSubject<Int> by lazy { PublishSubject.create<Int>() } } Master - Slave Test Application
  30. class FirebaseCloudMessageService : FirebaseMessagingService() { override fun onMessageReceived(remoteMessage: RemoteMessage?) {

    remoteMessage?.run { Timber.d("onMessageReceived($this)") for ((key, value) in this.data) Timber.d("[data] $key => $value") this.data["id"]?.toInt()?.let { if (BuildConfig.SLAVE) FirebaseCloudMessageForSlave.id = it } ?: Timber.d("Value Not Found : ${this.data} -> ${this.data["id"]}") } } } // in activity private val disposables by lazy { CompositeDisposable() } override fun onResume() { super.onResume() FirebaseCloudMessageForSlave.publisher .filter { BuildConfig.SLAVE } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ id -> this.findViewById<View?>(id)?.performClick() }, { Timber.e(it) }) .also { disposables.add(it) } } Master - Slave Test Application
  31. private fun View.taskOnClick(title: Any, block: () -> Unit) { this.setOnClickListener

    { logging(title, TextLogData.Type.TITLE) block() if (BuildConfig.MASTER) { logging("Sending Click Command ${this.javaClass}(#${this.id}) to All Slaves...") api.clickAll(this.id) .map { it.slaves } .flatMapIterable { slaves -> slaves } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ logging("Command Success to ${it.model}”) }, { Timber.e(it) }) } } } // in activity btn_bitsound_start_single_detection.run { Picasso.with(activity).load(R.drawable.ic_start_single_detection_white_24dp) .transform(ColorFilterTransformation(GREEN)) .into(this) this.taskOnClick(R.string.bitsound_single_start) { Bitsound.with(this).startSingleDetection(BitsoundContentsReceiver::class.java) echo activity } } Master - Slave Test Application