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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. View Slide

  6. ϑϦϧ

    View Slide

  7. View Slide

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

    View Slide

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

    View Slide

  10. ͓΅͍͑ͯ·͔͢

    Titanium Mobile

    View Slide

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

    View Slide

  12. ϑϦϧ for Android
    Java!

    View Slide

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

    View Slide

  14. v1.0

    View Slide

  15. Լλϒ!!!

    View Slide

  16. View Slide

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

    View Slide

  18. • 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 Slide

  19. • 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. View Slide

  29. View Slide

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

    View Slide

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

    View Slide

  32. 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 Slide

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

    View Slide


  34. v2.3

    View Slide


  35. v3.0

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  41. 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 Slide

  42. 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 Slide

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

    View Slide


  44. v4.0

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  49. 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 Slide

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

    View Slide

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

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

    View Slide

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

    View Slide

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

    View Slide

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

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

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

    View Slide

  55. 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 Slide

  56. 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 Slide

  57. 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 Slide

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

    View Slide

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

    View Slide

  60. RxJavaͷಋೖ

    View Slide

  61. 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 Slide

  62. 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 Slide

  63. 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 Slide

  64. 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 Slide

  65. 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 Slide

  66. 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 Slide

  67. 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 Slide

  68. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  81. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  85. READMEͷ੔උ

    View Slide

  86. JavaDocͷ੔උ

    View Slide

  87. JavaDocͷ੔උ

    View Slide

  88. @colorͷ੔උ

    View Slide

  89. @colorͷ੔උ

    View Slide

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

    View Slide

  91. AnnotationΛੵۃతʹ࢖͏

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  98. CI

    View Slide

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

    View Slide

  100. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  104. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  112. ςετγʔτ

    View Slide

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

    View Slide

  114. View Slide

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

    View Slide

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

    View Slide

  117. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  125. ໨తผνʔϜԽ

    View Slide

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

    View Slide

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

    Android MରԠ͠ͳ͍ͱ…

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  137. View Slide

  138. View Slide

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

    View Slide



  140. View Slide

  141. ·ͱΊ

    View Slide

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

    View Slide

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

    View Slide