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

4年続くアプリにおけるチーム開発 #DroidKaigi 2017

4年続くアプリにおけるチーム開発 #DroidKaigi 2017

2017/3/10
#DroidKaigi 2017 でお話した「フリル」のチーム開発に関する資料です

Tatsuya Arai

March 10, 2017
Tweet

More Decks by Tatsuya Arai

Other Decks in Technology

Transcript

  1. 4೥ଓ͘ΞϓϦʹ͓͚Δ
    νʔϜ։ൃ
    @cutmail
    ϋογϡλάɿ#DroidKaigi2

    View full-size slide

  2. ର৅ऀ
    • AndroidΞϓϦͷνʔϜ։ൃΛ͍ͯ͠Δ
    • AndroidΞϓϦͷνʔϜ։ൃʹڵຯ͕͋Δ
    • AndroidΞϓϦͷνʔϜ։ൃΛ͍ͯͯ͠Կ͔
    ࠔ͍ͬͯΔ

    View full-size slide

  3. ΞδΣϯμ
    • ϑϦϧAndroid൛ͷي੻
    • ϑϦϧʹ͓͚Δ։ൃ
    • νʔϜ։ൃΛ͏·͘ճͨ͢Ίʹ΍͖ͬͯͨ͜
    ͱ

    View full-size slide

  4. @cutmail
    • גࣜձࣾFablic Co-Founder/Engineer
    • Android / iOS / Ruby on Rails

    View full-size slide

  5. ϑϦϧ
    • ೔ຊॳͷϑϦϚΞϓϦ
    • ౰ॳ͸ঁੑݶఆɺͷͪʹஉੑʹղ์
    • 2012೥7݄ʹiOS൛͕ϦϦʔε

    View full-size slide

  6. ϑϦϧ for iOS
    • ౰࣌͸Titanium MobileͰ։ൃ։࢝
    • ։ൃظؒ໿3ϲ݄
    • ͷͪʹ༷ʑͳࣄ৘ͰωΠςΟϒԽ

    View full-size slide

  7. ͓΅͍͑ͯ·͔͢

    Titanium Mobile

    View full-size slide

  8. Titanium Mobile
    • ౰࣌͸JavascriptͰΫϩεϓϥοτϑΥʔϜ։
    ൃ͕Ͱ͖ΔͱݴΘΕ͍ͯͨ
    • ౰࣌ͷTitanium MobileͰͷAndroidΞϓϦ։
    ൃ͸

    View full-size slide

  9. ϑϦϧ for Android
    Java!

    View full-size slide

  10. ϑϦϧ for Android
    • 2012೥11݄6೔ ॳgitίϛοτ
    • 2013೥1݄29೔ v1.0ϦϦʔε
    • ࠷ۙKotlin͕ಋೖ͞Ε·ͨ͠

    View full-size slide

  11. • ϑϦϧAndroid൛ͷي੻
    • ϑϦϧʹ͓͚Δ։ൃ
    • νʔϜ։ൃΛ͏·͘ճͨ͢Ίʹ΍͖ͬͯͨ͜
    ͱ
    ΞδΣϯμ

    View full-size slide

  12. • v1.0
    • v2.1
    • ެࣜγϣοϓػೳ௥Ճ
    • v2.3
    • ActionBarԽ
    • σβΠϯϦχϡʔΞϧ
    • v2.5
    • FrilAPIClient΁ͷҠߦ
    • v3.0
    • UIϦχϡʔΞϧ
    • v3.6
    • ͓͢͢ΊϢʔβʔϦ
    χϡʔΞϧ
    • v4.0
    • UIϦχϡʔΞϧ
    • v4.1.2
    • RxAndroid1.0ʹߋ৽
    • v4.3
    • Android WearΞϓϦ
    ͷ௥Ճ
    • v5.0
    • ৭ݕࡧػೳͷ௥Ճ
    • v5.2
    • ͕͢͞ը໘ϦχϡʔΞ
    ϧ
    • v5.3.2
    • νϟοταϙʔτ
    • v5.5.0
    • FCM΁ͷҠߦ
    • v5.7.0
    • λΠϜϥΠϯɾ͕͢͞
    ը໘ϦχϡʔΞϧ
    • v6.0
    • v6.0.0 BIϦχϡʔΞ
    ϧɾϒϥϯυ&ੑผબ
    ୒ഇࢭ
    • v6.4
    • SMSೝূ

    View full-size slide

  13. • v1.0
    • v2.1
    • ެࣜγϣοϓػೳ௥Ճ
    • v2.3
    • ActionBarԽ
    • σβΠϯϦχϡʔΞϧ
    • v2.5
    • FrilAPIClient΁ͷҠߦ
    • v3.0
    • UIϦχϡʔΞϧ
    • v3.6
    • ͓͢͢ΊϢʔβʔϦ
    χϡʔΞϧ
    • v4.0
    • UIϦχϡʔΞϧ
    • v4.1.2
    • RxAndroid1.0ʹߋ৽
    • v4.3
    • Android WearΞϓϦ
    ͷ௥Ճ
    • v5.0
    • ৭ݕࡧػೳͷ௥Ճ
    • v5.2
    • ͕͢͞ը໘ϦχϡʔΞ
    ϧ
    • v5.3.2
    • νϟοταϙʔτ
    • v5.5.0
    • FCM΁ͷҠߦ
    • v5.7.0
    • λΠϜϥΠϯɾ͕͢͞
    ը໘ϦχϡʔΞϧ
    • v6.0
    • BIϦχϡʔΞϧɾϒϥ
    ϯυ&ੑผબ୒ഇࢭ
    • v6.4
    • SMSೝূ

    View full-size slide

  14. ΞϓϦΞΠίϯͷมԽ

    View full-size slide

  15. ϑϦϧͷྺ࢙͸
    ϦχϡʔΞϧͷྺ࢙

    View full-size slide

  16. ϦχϡʔΞϧ͸༷ʑͳ൓Ԡ͕
    ى͖Δ

    View full-size slide

  17. https://speakerdeck.com/shoby/yuzanishou-keru-rerare-wen-ti-woqi-kosiduraida-gui-mo-riniyuarufalsejin-mefang

    View full-size slide

  18. • v1.0~
    • v2.0~
    • v3.5~

    View full-size slide

  19. ։ൃମ੍
    • AndroidΞϓϦΤϯδχΞ 1.5໊
    • ࣗ෼͸iOSΛ΍Γͳ͕ΒยखؒAndroid
    • σβΠφʔ 1໊
    v1.0~v1.1
    ♂♂

    View full-size slide

  20. ։ൃ؀ڥ
    • Eclipse
    • Bitbucket «
    v1.0~v1.1

    View full-size slide

  21. ։ൃϑϩʔ
    • ϓϧϦΫΤετɺίʔυϨϏϡʔ͸ͳ͠
    • developϒϥϯν͔ΒͦΕͧΕϒϥϯνΛ੾ͬ
    ֤ͯࣗͰϚʔδ
    • جຊతʹ͸iOSͷػೳΛͦͷ··Ҡ২
    v1.0~v1.1

    View full-size slide

  22. ΞʔΩςΫνϟ
    • Activity
    • DB
    • Content Provider
    • ը૾ಡΈࠐΈ
    • URLConnectionʹΑΔࣗલ࣮૷
    v1.0~v1.1

    View full-size slide

  23. ΞʔΩςΫνϟ
    • API
    • AsyncTaskLoaderϕʔε
    • ը໘ؒͷΠϕϯτ௨஌
    • startActivityForResult
    • ্෦ͷόʔ͸ࣗલ
    v1.0~v1.1

    View full-size slide

  24. v1.0~v1.1
    public Loader onCreateLoader(int index, Bundle args) {
    HashMap params = new HashMap();
    params.put("method", "0");
    params.put("grid_flag", "0");
    params.put("pos", "0");
    Loader loader = new JSONLoader(this, "POST",
    “/timeline", params);
    loader.forceLoad();
    return loader;
    }
    @Override
    public void onLoadFinished(Loader arg0, JSONObject response) {
    if (response == null) return;
    mAdapter.loadFromJSON(response);
    if (mAdapter.getCount() > 0) {
    mListView.setAdapter(mAdapter);
    mListView.invalidate();
    }
    }
    AsyncTaskLoader
    APIίʔϧ
    APIϨεϙϯε

    View full-size slide

  25. • v1.0~v1.1
    • v2.0~
    • v3.5~

    View full-size slide

  26. ମ੍
    • AndroidΞϓϦΤϯδχΞ 5໊
    • σβΠφʔ 1໊
    v2.0~v3.4
    ♂♂♂
    ♂♂

    View full-size slide

  27. ։ൃ؀ڥ
    • GitHub
    • Android Studio
    • Gradle
    • CI (Travis-CI)
    v2.0~

    View full-size slide

  28. ։ൃϑϩʔ
    • ϓϧϦΫΤετಋೖ
    • ίʔυϨϏϡʔಋೖ
    v2.0~

    View full-size slide

  29. ΞʔΩςΫνϟ
    • Activity + Fragment
    • ԣը໘ɺλϒϨοτରԠͷͨΊʹFragmentͷಋೖ
    • DB
    • Content Provider
    • ը૾ಡΈࠐΈ
    • Picasso
    v2.0~

    View full-size slide

  30. ΞʔΩςΫνϟ
    • API
    • android-async-http
    • EventBus
    • Otto
    v2.0~

    View full-size slide

  31. public RequestHandle getItemDetail(int itemId, final SingleModelCallback
    callback) {

    RequestParams params = new RequestParams(baseParams);

    params.put("item_id", String.valueOf(itemId));


    return httpClient.get(baseUrl + "/api/item", params, new JsonHttpResponseHandler() {

    @Override

    public void onSuccess(int statusCode, Header[] headers, JSONObject response) {

    ItemDetail itemDetail = gson.fromJson(response.toString(), ItemDetail.class);

    callback.success(itemDetail);

    }


    @Override

    public void onFailure(int statusCode, Throwable e, JSONObject errorResponse) {

    callback.failure(statusCode, e, errorResponse);

    }

    });

    }
    android-async-http v2.0~

    View full-size slide

  32. private void getItemDetail() {
    apiClient.getItemDetail(mItem.getId(),new
    FrilAPIClient.SingleModelCallback() {
    @Override
    public void success(ItemDetail itemDetail) {
    setDetailView(itemDetail);
    }
    @Override
    public void failure(int statusCode, Throwable error,
    JSONObject errorResponse) {
    // Τϥʔॲཧ
    }
    });
    }
    android-async-http v2.0~

    View full-size slide

  33. • v1.0~
    • v2.0~
    • v3.5~

    View full-size slide

  34. ։ൃମ੍
    • AndroidΞϓϦΤϯδχΞ 2໊
    • σβΠφʔ 1໊
    v3.5~
    ♂♂

    View full-size slide

  35. ։ൃ؀ڥ
    v3.5~
    • GitHub
    • Android Studio
    • Gradle
    • CI (CircleCI)

    View full-size slide

  36. ΞʔΩςΫνϟ
    • Activity + Fragment
    • DB
    • Content Provider
    v3.5~

    View full-size slide

  37. ΞʔΩςΫνϟ
    • API
    • Retrofit + RxJava
    v3.5~

    View full-size slide

  38. Observable observable =
    FrilServiceCreator.createFrilService(activity).getItemDetail(itemId);

    return observable.subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(new Action1() {

    @Override

    public void call(ItemDetail itemDetail) {
    setItemDetail(itemDetail);

    }

    }, new Action1() {

    @Override

    public void call(Throwable throwable) {

    Timber.e(throwable, throwable.getMessage());

    }

    });
    Retrofit + RxJava v3.5~
    @GET("/api/item")

    Observable getItemDetail(

    @Query("item_id") Integer itemId

    );

    View full-size slide

  39. ΞʔΩςΫνϟͷมભ

    View full-size slide

  40. ΞʔΩςΫνϟͷมભ

    ωοτϫʔΫ "TZOD5BTL-PBEFS
    BOESPJE
    BTZODIUUQ
    3FUSPpU
    ඇಉظॲཧ
    ΠϯλϑΣʔε
    -PBEFS $BMMCBDL 3Y+BWB
    &WFOU#VT TUBSU"DUJWJUZ'PS3FTVMU 0UUP 0UUP
    ը૾ 63-$POOFDUJPO 1JDBTTP 1JDBTTP

    View full-size slide

  41. RxJavaΛಋೖ͢Δ·Ͱ͸…

    View full-size slide

  42. ྫ͑͹ɺΞϓϦ಺ͷϚελσʔλ
    ʢۜߦɺϒϥϯυʣ
    ͷߋ৽

    View full-size slide

  43. ྫʣϚελσʔλͷߋ৽ॲཧ
    1. Ϛελσʔλͷߋ৽ΛνΣοΫ

    2. ߋ৽͕͋Ε͹ඞཁͳ߲໨Λߋ৽͢Δ

    3. શͯͷσʔλͷߋ৽͕׬ྃͨ͠Βݺͼग़͠ݩʹ໭͢

    View full-size slide

  44. RxJavaಋೖલ
    public class MigrateMasterDataTask extends AsyncTask {
    @Override

    protected Boolean doInBackground(Void... params) {
    final FrilService frilService = FrilServiceCreator.createFrilService(context);


    //ϦϞʔτ͔ΒͷऔΓࠐΈ͕ඞཁ͔νΣοΫ

    Call call = frilService.checkNeedDatabaseUpdateSync(

    AppPrefs.getBrandVersion(context),

    AppPrefs.getBankVersion(context));


    DatabaseUpdate databaseUpdate = call.execute().body();

    if (databaseUpdate == null) {

    return false;

    }


    // ϦϞʔτ͔ΒͷऔΓࠐΈ͕ඞཁ

    boolean updateBrand = databaseUpdate.isNeedForBrand();

    boolean updateBank = databaseUpdate.isNeedForBank();


    if (!updateBrand && !updateBank) {

    return true;

    }


    //ϦϞʔτͷσʔλΛ SQLite ʹҠߦ

    return syncFromRemote(updateBrand, updateBank);
    }
    }

    View full-size slide

  45. RxJavaಋೖલ
    public class MigrateMasterDataTask extends AsyncTask {
    @Override

    protected Boolean doInBackground(Void... params) {
    final FrilService frilService = FrilServiceCreator.createFrilService(context);


    //ϦϞʔτ͔ΒͷऔΓࠐΈ͕ඞཁ͔νΣοΫ

    Call call = frilService.checkNeedDatabaseUpdateSync(

    AppPrefs.getBrandVersion(context),

    AppPrefs.getBankVersion(context));


    DatabaseUpdate databaseUpdate = call.execute().body();

    if (databaseUpdate == null) {

    return false;

    }


    // ϦϞʔτ͔ΒͷऔΓࠐΈ͕ඞཁ

    boolean updateBrand = databaseUpdate.isNeedForBrand();

    boolean updateBank = databaseUpdate.isNeedForBank();


    if (!updateBrand && !updateBank) {

    return true;

    }


    //ϦϞʔτͷσʔλΛ SQLite ʹҠߦ

    return syncFromRemote(updateBrand, updateBank);
    }
    }

    View full-size slide

  46. RxJavaಋೖલ
    public class MigrateMasterDataTask extends AsyncTask {
    @Override

    protected Boolean doInBackground(Void... params) {
    final FrilService frilService = FrilServiceCreator.createFrilService(context);


    //ϦϞʔτ͔ΒͷऔΓࠐΈ͕ඞཁ͔νΣοΫ

    Call call = frilService.checkNeedDatabaseUpdateSync(

    AppPrefs.getBrandVersion(context),

    AppPrefs.getBankVersion(context));


    DatabaseUpdate databaseUpdate = call.execute().body();

    if (databaseUpdate == null) {

    return false;

    }


    // ϦϞʔτ͔ΒͷऔΓࠐΈ͕ඞཁ

    boolean updateBrand = databaseUpdate.isNeedForBrand();

    boolean updateBank = databaseUpdate.isNeedForBank();


    if (!updateBrand && !updateBank) {

    return true;

    }


    //ϦϞʔτͷσʔλΛ SQLite ʹҠߦ

    return syncFromRemote(updateBrand, updateBank);
    }
    }

    View full-size slide

  47. RxJavaಋೖલ
    private boolean syncFromRemote(boolean isNeedUpdateBrand, boolean isNeedUpdateBank) {
    if (isNeedUpdateBrand) {
    // ωοτϫʔΫ͔ΒϒϥϯυϚελΛಉظऔಘ
    }
    if (isNeedUpdateBank) {
    // ωοτϫʔΫ͔ΒۜߦϚελΛಉظऔಘ
    }
    if (brandVersion != null) {
    // ΞϓϦ಺ʹ࠷৽ͷόʔδϣϯ৘ใΛอଘ
    }
    if (bankVersion != null) {
    // ΞϓϦ಺ʹ࠷৽ͷόʔδϣϯ৘ใΛอଘ
    }
    return true;
    }

    View full-size slide

  48. ॲཧͷྲྀΕ͕௥͍ͮΒ͍
    https://www.flickr.com/photos/eughenes/3758142701

    View full-size slide

  49. RxJavaͷಋೖ

    View full-size slide

  50. RxJavaಋೖޙ
    public Completable migrateMasterData() {

    final String brandVersion = AppPrefs.getBrandVersion(context);

    final String bankVersion = AppPrefs.getBankVersion(context);

    return frilService.checkDatabaseUpdateNeeded(brandVersion, bankVersion)

    .flatMap(update -> {

    List completables = new ArrayList<>();

    if (update.isNeedForBrand()) {

    completables.add(migrateBrandsFromRemote().subscribeOn(Schedulers.io()));

    }

    if (update.isNeedForBank()) {

    completables.add(migrateBanksFromRemote().subscribeOn(Schedulers.io()));

    }

    return Completable.merge(completables).toObservable();

    })

    .onErrorResumeNext(throwable -> {

    return Observable.error(throwable);

    })

    .toCompletable();

    }
    Subscription subscription = helper.migrateMasterData()

    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(this::dismissProgressDialog, throwable -> {

    Timber.e(throwable, throwable.getMessage());

    });

    subscriptions.add(subscription);

    View full-size slide

  51. RxJavaಋೖޙ
    public Completable migrateMasterData() {

    final String brandVersion = AppPrefs.getBrandVersion(context);

    final String bankVersion = AppPrefs.getBankVersion(context);

    return frilService.checkDatabaseUpdateNeeded(brandVersion, bankVersion)

    .flatMap(update -> {

    List completables = new ArrayList<>();

    if (update.isNeedForBrand()) {

    completables.add(migrateBrandsFromRemote().subscribeOn(Schedulers.io()));

    }

    if (update.isNeedForBank()) {

    completables.add(migrateBanksFromRemote().subscribeOn(Schedulers.io()));

    }

    return Completable.merge(completables).toObservable();

    })

    .onErrorResumeNext(throwable -> {

    return Observable.error(throwable);

    })

    .toCompletable();

    }
    Subscription subscription = helper.migrateMasterData()

    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(this::dismissProgressDialog, throwable -> {

    Timber.e(throwable, throwable.getMessage());

    });

    subscriptions.add(subscription);

    View full-size slide

  52. RxJavaಋೖޙ
    public Completable migrateMasterData() {

    final String brandVersion = AppPrefs.getBrandVersion(context);

    final String bankVersion = AppPrefs.getBankVersion(context);

    return frilService.checkDatabaseUpdateNeeded(brandVersion, bankVersion)

    .flatMap(update -> {

    List completables = new ArrayList<>();

    if (update.isNeedForBrand()) {

    completables.add(migrateBrandsFromRemote().subscribeOn(Schedulers.io()));

    }

    if (update.isNeedForBank()) {

    completables.add(migrateBanksFromRemote().subscribeOn(Schedulers.io()));

    }

    return Completable.merge(completables).toObservable();

    })

    .onErrorResumeNext(throwable -> {

    return Observable.error(throwable);

    })

    .toCompletable();

    }
    Subscription subscription = helper.migrateMasterData()

    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(this::dismissProgressDialog, throwable -> {

    Timber.e(throwable, throwable.getMessage());

    });

    subscriptions.add(subscription);

    View full-size slide

  53. RxJavaಋೖޙ
    public Completable migrateMasterData() {

    final String brandVersion = AppPrefs.getBrandVersion(context);

    final String bankVersion = AppPrefs.getBankVersion(context);

    return frilService.checkDatabaseUpdateNeeded(brandVersion, bankVersion)

    .flatMap(update -> {

    List completables = new ArrayList<>();

    if (update.isNeedForBrand()) {

    completables.add(migrateBrandsFromRemote().subscribeOn(Schedulers.io()));

    }

    if (update.isNeedForBank()) {

    completables.add(migrateBanksFromRemote().subscribeOn(Schedulers.io()));

    }

    return Completable.merge(completables).toObservable();

    })

    .onErrorResumeNext(throwable -> {

    return Observable.error(throwable);

    })

    .toCompletable();

    }
    Subscription subscription = helper.migrateMasterData()

    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(this::dismissProgressDialog, throwable -> {

    Timber.e(throwable, throwable.getMessage());

    });

    subscriptions.add(subscription);

    View full-size slide

  54. RxJavaಋೖޙ
    public Completable migrateMasterData() {

    final String brandVersion = AppPrefs.getBrandVersion(context);

    final String bankVersion = AppPrefs.getBankVersion(context);

    return frilService.checkDatabaseUpdateNeeded(brandVersion, bankVersion)

    .flatMap(update -> {

    List completables = new ArrayList<>();

    if (update.isNeedForBrand()) {

    completables.add(migrateBrandsFromRemote().subscribeOn(Schedulers.io()));

    }

    if (update.isNeedForBank()) {

    completables.add(migrateBanksFromRemote().subscribeOn(Schedulers.io()));

    }

    return Completable.merge(completables).toObservable();

    })

    .onErrorResumeNext(throwable -> {

    return Observable.error(throwable);

    })

    .toCompletable();

    }
    Subscription subscription = helper.migrateMasterData()

    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(this::dismissProgressDialog, throwable -> {

    Timber.e(throwable, throwable.getMessage());

    });

    subscriptions.add(subscription);

    View full-size slide

  55. RxJavaಋೖޙ
    public Completable migrateMasterData() {

    final String brandVersion = AppPrefs.getBrandVersion(context);

    final String bankVersion = AppPrefs.getBankVersion(context);

    return frilService.checkDatabaseUpdateNeeded(brandVersion, bankVersion)

    .flatMap(update -> {

    List completables = new ArrayList<>();

    if (update.isNeedForBrand()) {

    completables.add(migrateBrandsFromRemote().subscribeOn(Schedulers.io()));

    }

    if (update.isNeedForBank()) {

    completables.add(migrateBanksFromRemote().subscribeOn(Schedulers.io()));

    }

    return Completable.merge(completables).toObservable();

    })

    .onErrorResumeNext(throwable -> {

    return Observable.error(throwable);

    })

    .toCompletable();

    }
    Subscription subscription = helper.migrateMasterData()

    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(this::dismissProgressDialog, throwable -> {

    Timber.e(throwable, throwable.getMessage());

    });

    subscriptions.add(subscription);

    View full-size slide

  56. RxJavaಋೖޙ
    public Completable migrateMasterData() {

    final String brandVersion = AppPrefs.getBrandVersion(context);

    final String bankVersion = AppPrefs.getBankVersion(context);

    return frilService.checkDatabaseUpdateNeeded(brandVersion, bankVersion)

    .flatMap(update -> {

    List completables = new ArrayList<>();

    if (update.isNeedForBrand()) {

    completables.add(migrateBrandsFromRemote().subscribeOn(Schedulers.io()));

    }

    if (update.isNeedForBank()) {

    completables.add(migrateBanksFromRemote().subscribeOn(Schedulers.io()));

    }

    return Completable.merge(completables).toObservable();

    })

    .onErrorResumeNext(throwable -> {

    return Observable.error(throwable);

    })

    .toCompletable();

    }
    Subscription subscription = helper.migrateMasterData()

    .subscribeOn(Schedulers.io())

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe(this::dismissProgressDialog, throwable -> {

    Timber.e(throwable, throwable.getMessage());

    });

    subscriptions.add(subscription);

    View full-size slide

  57. ωοτϫʔΫϨΠϠͷ੔උʹΑ
    ΓɺॲཧͷྲྀΕ͕Θ͔Γ΍͘͢
    ͳͬͨ

    View full-size slide

  58. RetrofitʹΑΓΤϯυϙΠϯτఆ
    ٛͷίʔυ͕গͳ͘ͳͬͨ

    View full-size slide

  59. ΞʔΩςΫνϟ΍υΩϡϝϯτ
    ͷ੔උ͕νʔϜͷ։ൃ଎౓ʹӨ
    ڹ͢Δ

    View full-size slide

  60. ͜͜·Ͱ͕ϑϦϧAndroid൛
    ͷي੻

    View full-size slide

  61. • ϑϦϧAndroid൛ͷي੻
    • ϑϦϧʹ͓͚Δ։ൃ
    • νʔϜ։ൃΛ͏·͘ճͨ͢Ίʹ΍͖ͬͯͨ͜
    ͱɺ΍ͬͯྑ͔ͬͨ͜ͱ
    ΞδΣϯμ

    View full-size slide

  62. ϑϦϧAndroid൛ͷ։ൃ
    • Issue؅ཧ
    • ϓϧϦΫΤετ
    • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ
    • CI
    • ϢʔβʔΠϯλϏϡʔ
    • QA
    • ϦϦʔε࡞ۀ
    • ਺ࣈͷܭଌ

    View full-size slide

  63. ϑϦϧAndroid൛ͷ։ൃ
    • Issue؅ཧ
    • ϓϧϦΫΤετ
    • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ
    • CI
    • ϢʔβʔΠϯλϏϡʔ
    • QA
    • ϦϦʔε࡞ۀ
    • ਺ࣈͷܭଌ

    View full-size slide

  64. Issue؅ཧ
    • LabelΛ׆༻
    • ready͸ίʔυϨϏϡʔ
    ͯ͠OK

    View full-size slide

  65. ϑϦϧAndroid൛ͷ։ൃ
    • Issue؅ཧ
    • ϓϧϦΫΤετ
    • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ
    • CI
    • ϢʔβʔΠϯλϏϡʔ
    • QA
    • ϦϦʔε
    • ਺ࣈͷܭଌ

    View full-size slide

  66. ϓϧϦΫΤετ
    • GitHubͷςϯϓϨʔτΛ׆༻
    • ֬ೝखॱ͕ॏཁ

    View full-size slide

  67. ϑϦϧAndroid൛ͷ։ൃ
    • Issue؅ཧ
    • ϓϧϦΫΤετ
    • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ
    • CI
    • ϢʔβʔΠϯλϏϡʔ
    • QA
    • ϦϦʔε
    • ਺ࣈͷܭଌ

    View full-size slide

  68. ίʔσΟϯάن໿ͱυΩϡϝϯτͷ੔උ
    • ίʔσΟϯάن໿ΛఆΊ͍ͯ·͢
    • CONTRIBUTING.md
    • ϓϧϦΫΤετΛग़͢ࡍʹϦϯΫ͕දࣔ͞
    ΕΔ

    View full-size slide

  69. جຊతʹCookpadͷ
    ίʔσΟϯάن໿ʹ४ڌ͢Δ

    View full-size slide

  70. https://github.com/cookpad/styleguide

    View full-size slide

  71. ࡉ͔͍ͱ͜ΖͰ໎Θͳ͍Α͏
    ʹίʔσΟϯάن໿Λ੔උ

    View full-size slide

  72. READMEͷ੔උ

    View full-size slide

  73. JavaDocͷ੔උ

    View full-size slide

  74. JavaDocͷ੔උ

    View full-size slide

  75. @colorͷ੔උ

    View full-size slide

  76. @colorͷ੔උ

    View full-size slide

  77. AnnotationΛੵۃతʹ࢖͏
    dependencies { compile 'com.android.support:support-annotations:24.2.0' }
    void setActionBarAlpha(@IntRange(from = 0x0, to = 0xFF) int alpha) {

    View full-size slide

  78. AnnotationΛੵۃతʹ࢖͏

    View full-size slide

  79. https://developer.android.com/studio/write/
    annotations.html
    Improve Code Inspection
    with Annotations

    View full-size slide

  80. ϝϯςφϯε͠΍͍͢ঢ়ଶʹ
    ͓ͯ͘͠

    View full-size slide

  81. ։ൃ͕εϜʔζʹਐΈग़͢ͱ
    ى͖Δͷ͕

    View full-size slide

  82. ϓϧϦΫΤετཷ·Δ໰୊

    View full-size slide

  83. ϓϧϦΫΤετཷ·Δ໰୊
    • ཷ·͍ͬͯΔϓϧϦΫΤετΛSlackʹ௨஌
    • ϨϏϡʔ͠ͳ͍ͱຖ೔ͲΜͲΜ૿͍͑ͯ͘

    View full-size slide

  84. ϑϦϧAndroid൛ͷ։ൃ
    • Issue؅ཧ
    • ϓϧϦΫΤετ
    • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ
    • CI
    • ϢʔβʔΠϯλϏϡʔ
    • QA
    • ϦϦʔε
    • ਺ࣈͷܭଌ

    View full-size slide

  85. http://in.fablic.co.jp/entry/circle-ci-in-android-project

    View full-size slide

  86. ϕʔλ൛ͷࣾ಺഑෍
    • Fabric betaͰࣾ಺Ϣʔβʔʹ഑෍
    • developϒϥϯνʹmerge͞ΕΔ౓ʹ࠷৽൛͕
    ഑෍͞ΕΔ

    View full-size slide

  87. Fabric betaʹΑΔβ൛഑෍
    webhook
    push

    View full-size slide

  88. ϦϦʔε൛ͱ͸ผΞϓϦͱͯ͠഑෍

    View full-size slide

  89. ϑϦϧAndroid൛ͷ։ൃ
    • Issue؅ཧ
    • ϓϧϦΫΤετ
    • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ
    • CI
    • ϢʔβʔΠϯλϏϡʔ
    • QA
    • ϦϦʔε
    • ਺ࣈͷܭଌ

    View full-size slide

  90. ϢʔβʔΠϯλϏϡʔ

    View full-size slide

  91. ϢʔβʔΠϯλϏϡʔ
    • iOS൛ϦϦʔεͷࡍʹ͸100ਓ͘Β͍Ϣʔβʔ
    Λั·͑ͯΠϯλϏϡʔΛ࣮ࢪ
    • ϑϦϧ͸Ϣʔβʔʹฉ͘จԽ

    View full-size slide

  92. ϢʔβʔΠϯλϏϡʔ
    • ຖճͰ͸ͳ͍͕େ͖ͳϦχϡʔΞϧͳͲͷࡍ
    ͸ߦ͏
    • ࣾ಺ͷCSελοϑ͸શһϑϦϧϢʔβʔͷͨ
    Ίɺ͙͢ʹΠϯλϏϡʔ͕Մೳ
    • ελϯσΟϯάσεΫͰΧδϡΞϧʹΠϯλ
    Ϗϡʔ

    View full-size slide

  93. ϢʔβʔΠϯλϏϡʔ
    • QAલޙʹΠϯλϏϡʔΛͨ͠Γ΋͢Δ
    • ΠϯλϏϡʔͷ݁Ռɺ࢓༷Λม͑Δ͜ͱ΋͋
    Δ
    • ੈʹग़Δલʹ࣮ࡍͷϢʔβʔͷ੠͕ฉ͚Δͨ
    ΊΤϯδχΞͱͯ͠΋҆৺

    View full-size slide

  94. ϑϦϧAndroid൛ͷ։ൃ
    • Issue؅ཧ
    • ϓϧϦΫΤετ
    • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ
    • CI
    • ϢʔβʔΠϯλϏϡʔ
    • QA
    • ϦϦʔε
    • ਺ࣈͷܭଌ

    View full-size slide

  95. QA
    • ΤϯδχΞɺCSελοϑɺσβΠφʔɺϓϩμΫ
    τΦʔφʔ͕ࢀՃ
    • ςετγʔτΛࣄલʹΤϯδχΞ͕࡞੒͠ɺ߲໨
    ʹԊͬͯಈ࡞νΣοΫΛ͍ͯ͘͠
    • ΞϓϦͷΫΦϦςΟνΣοΫ
    • ࢓༷֬ೝ΋݉Ͷ͍ͯΔ

    View full-size slide

  96. ςετγʔτ

    View full-size slide

  97. QAͷྲྀΕ
    ࣄલ४උ
    1. git-pr-releaseͰϦϦʔεʹ޲͚ͯQA༻ͷϓϧϦΫΤετΛ࡞Δ
    2. ΤϯδχΞ͕ϓϧϦΫΤετͷίϝϯτΛϕʔεʹQA։࢝·Ͱʹςετγʔτ
    Λهࡌ͢Δ
    3. SlackͰQAΛґཔ
    ౰೔
    1. ςετͷ֓ཁΛઆ໌
    2. ςετγʔτʹ͕ͨͬͯ͠ςετΛ͍ͯ͘͠
    3. ؾ͍ͮͨ͜ͱ͕͋Ε͹શͯϝϞΛ࢒͢

    View full-size slide

  98. ϑϦϧAndroid൛ͷ։ൃ
    • Issue؅ཧ
    • ϓϧϦΫΤετ
    • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ
    • CI
    • ϢʔβʔΠϯλϏϡʔ
    • QA
    • ϦϦʔε
    • ਺ࣈͷܭଌ

    View full-size slide

  99. ϦϦʔε
    .git-pr-templateʹϦϦʔεखॱΛهࡌ

    View full-size slide

  100. ϑϦϧAndroid൛ͷ։ൃ
    • Issue؅ཧ
    • ϓϧϦΫΤετ
    • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ
    • CI
    • ϢʔβʔΠϯλϏϡʔ
    • QA
    • ϦϦʔε
    • ਺ࣈͷܭଌ

    View full-size slide

  101. ΞϓϦ಺Πϕϯτͷܭଌ
    https://www.flickr.com/photos/helenanormark/9421984744

    View full-size slide

  102. ΞϓϦ಺Πϕϯτͷܭଌ
    • Google Analytics
    • Facebook Analytics
    • Fabric
    • Firebase→BigQuery
    • ޿ࠂSDK

    View full-size slide

  103. ෳ਺ͷSDKΛೖΕ͍ͯΔཧ༝
    • ֤αʔϏεʹΑͬͯݟ͑Δ΋ͷ͕ҧͬͨΓɺࢪ
    ࡦΛߟ͑ΔࡍʹݟΔ਺ࣈͷ֯౓͕ҧͬͨΓ͢Δ
    • Ұ͚ͭͩ਺஋͕ͣΕͨΓͯ͠΋ݕ஌͢Δ͜ͱ͕
    Ͱ͖Δ
    • αʔϏε্ॏཁͳKPI͸ಠࣗͷ؅ཧը໘Λ࡞ͬ
    ͯ΢Υον

    View full-size slide

  104. εΫϦʔϯΠϕϯτͷܭଌ
    • ΤϯδχΞͷख࡞ۀͰ֤Activityʹܭଌίʔυ
    ΛຒΊࠐΜͰ͍Δ
    • ͔ͭͯiOSͰػցతʹຒΊࠐΉ͜ͱΛ͕ͨ͠ɺ
    ूܭ݁Ռ͕ͪ͝Όͪ͝Όʹͳͬͯ͠·ͬͨ͜
    ͱ͕͋ͬͨ

    View full-size slide

  105. εΫϦʔϯΠϕϯτͷॏཁੑ
    • ػೳΛվળͨ͠Γɺ࢒͢ɺ࢒͞ͳ͍ͷٞ࿦Λ
    ͢Δͱ͖ͷࡐྉʹͳΔ
    • ਺ࣈΛϕʔεʹٞ࿦Λ͢Δ

    View full-size slide

  106. • ϑϦϧAndroid൛ͷي੻
    • ීஈͷ։ൃͷྲྀΕ
    • νʔϜ։ൃΛ͏·͘ճͨ͢Ίʹ΍ͬͯྑ͔ͬ
    ͨ͜ͱ
    ΞδΣϯμ

    View full-size slide

  107. ໨తผνʔϜԽ

    View full-size slide

  108. ~2015೥ࠒ·Ͱ
    • iOSνʔϜɺAndroidνʔϜɺServerνʔϜɺ
    σβΠφʔͷΑ͏ͳઐ໳৬ͰΘ͔Ε͍ͯͨ
    iOS Android Server
    ♂♂♂
    Design
    ♂♂♂ ♂♂♂ ♂♂♂

    View full-size slide

  109. ৽ػೳ΋௥Ճ͍͚ͨ͠Ͳɺ

    Android MରԠ͠ͳ͍ͱ…

    View full-size slide

  110. 2015೥ࠒ~
    • AνʔϜɺBνʔϜͱ͍͏໨తผͷνʔϜʹશͯͷ։ൃϝϯόʔ͕
    ॴଐ
    • ҕһձ੍
    • ΫϥΠΞϯτҕһձ
    • αʔόʔҕһձ
    • σβΠϯҕһձ
    • ෼ੳҕһձ

    View full-size slide

  111. ໨తผνʔϜ
    B
    A
    ♂♂♂♂ ♂ ♂♂♂ ♂ ♂
    ♂♂♂♂ ♂ ♂♂♂ ♂ ♂♂♂
    ♂♂
    iOS Android Server Design

    View full-size slide

  112. ໨తผνʔϜԽ
    • KPIผͷνʔϜʹͨ͜͠ͱͰ໨ඪ͕໌֬ʹͳͬ
    ͨ
    • Android͚ͩͱ͔Ͱ͸ͳ͘ɺ໨ඪΛୡ੒͢Δ
    ͨΊʹ෯޿͍εΩϧΛٻΊΒΕΔΑ͏ʹͳͬ
    ͨ

    View full-size slide

  113. ໨తผνʔϜԽ
    • ٕज़త՝୊ͳͲΛҕһձ͕੹೚Λ࣋ͭ͜ͱͰɺ
    ੹೚ൣғ͕ΑΓ໌֬ʹ
    • ྫʣAndroid 6.0ରԠɺRailsΞοϓσʔτ

    View full-size slide

  114. αϙʔτରԠͷ౰൪੍
    • CS͔Β࣭໰ͳͲ͕͋ͬͨΒΘ͔Δਓ͕౴͍͑ͯͨ
    • 2016೥ΑΓσΠϦʔͷ౰൪੍Λಋೖ
    • ຖேbot͕2໊બͼɺબ͹Εͨਓ͸ͦͷ೔Ұ೔αϙʔτ͔Β
    ͷ࣭໰ʹ౴͑Δ
    • Θ͔Βͳ͍৔߹͸Θ͔ΔਓʹΤεΫϩʔ
    • ରԠ಺༰͸Ͱ͖ΔݶΓwikiʹ࢒͢

    View full-size slide

  115. αϙʔτରԠͷ౰൪੍
    • ஌ࣝͷଐਓԽ͕ݮͬͨ
    • αʔϏε࢓༷ͷཧղ͕ਂ·ͬͨ
    • ྫ͑͹औҾपΓͷ࢓༷ͳͲ

    View full-size slide

  116. ϦϦʔεϊʔτΛΤϯδχΞ͕ॻ͘

    View full-size slide

  117. ϦϦʔεϊʔτΛΤϯδχΞ͕ॻ͘
    • ࣮૷Λ୲౰ͨ͠ΤϯδχΞΛத৺ͱͯ͠ϦϦʔ
    εϊʔτΛॻ͘
    • ਓؒຯͷ͋Δจষ
    • ࠷ऴతʹϥΠςΟϯάελοϑʹϨϏϡʔ͠
    ͯ΋Β͏

    View full-size slide

  118. PlayετΞͷϨϏϡʔ
    • AppFollowͱ͍͏αʔϏεΛಋೖ

    View full-size slide

  119. PlayετΞͷϨϏϡʔ
    • Slackͷνϟϯωϧʹਵ࣌ඈΜͰ͘Δ
    • ωΨςΟϒͳϨϏϡʔ͕͋Ε͹։ൃɺCSνʔϜ͕
    रͬͯվળʹ໾ཱͯΔ
    • CSνʔϜ಺ʹϨϏϡʔʹฦ৴͢Δ୲౰Λஔ͍͍ͯΔ
    • ΞϓϦͷධՁΛྑ͍ঢ়ଶʹอͭ

    View full-size slide

  120. ௕͘ଓ͘ΞϓϦΛ։ൃ͠ଓ͚Δ
    ʹ͸
    • ఆظతʹΞʔΩςΫνϟΛݟ௚͢
    • ن໿΍υΩϡϝϯτΛ੔උ͢Δ
    • ૝૾Ͱ͸ͳ͘ɺQA΍ϢʔβʔΠϯλϏϡʔͳ
    ͲͰ࣮ࡍͷϢʔβʔͷ੠Λฉ͖ɺαʔϏεʹ
    ൓ө͢Δ

    View full-size slide

  121. ͝੩ௌ͋Γ͕ͱ͏
    ͍͟͝·ͨ͠

    View full-size slide