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

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

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

Cb22bcbee04e5a2fb897d703e794ca8f?s=128

Tatsuya Arai

March 10, 2017
Tweet

Transcript

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

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

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

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

    on Rails
  5. None
  6. ϑϦϧ

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

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

  10. ͓΅͍͑ͯ·͔͢
 Titanium Mobile

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

  12. ϑϦϧ for Android Java!

  13. ϑϦϧ for Android • 2012೥11݄6೔ ॳgitίϛοτ • 2013೥1݄29೔ v1.0ϦϦʔε •

    ࠷ۙKotlin͕ಋೖ͞Ε·ͨ͠
  14. v1.0

  15. Լλϒ!!!

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

  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ೝূ
  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ೝূ
  20. ΞϓϦΞΠίϯͷมԽ

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

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

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

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

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

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

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

  28. None
  29. None
  30. ΞʔΩςΫνϟ • Activity • DB • Content Provider • ը૾ಡΈࠐΈ

    • URLConnectionʹΑΔࣗલ࣮૷ v1.0~v1.1
  31. ΞʔΩςΫνϟ • API • AsyncTaskLoaderϕʔε • ը໘ؒͷΠϕϯτ௨஌ • startActivityForResult •

    ্෦ͷόʔ͸ࣗલ v1.0~v1.1
  32. v1.0~v1.1 public Loader<JSONObject> onCreateLoader(int index, Bundle args) { HashMap<String, String>

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

  34.  v2.3

  35.  v3.0

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

  37. ։ൃ؀ڥ • GitHub • Android Studio • Gradle • CI

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

  39. ΞʔΩςΫνϟ • Activity + Fragment • ԣը໘ɺλϒϨοτରԠͷͨΊʹFragmentͷಋೖ • DB •

    Content Provider • ը૾ಡΈࠐΈ • Picasso v2.0~
  40. ΞʔΩςΫνϟ • API • android-async-http • EventBus • Otto v2.0~

  41. public RequestHandle getItemDetail(int itemId, final SingleModelCallback<ItemDetail> 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~
  42. private void getItemDetail() { apiClient.getItemDetail(mItem.getId(),new FrilAPIClient.SingleModelCallback<ItemDetail>() { @Override public void

    success(ItemDetail itemDetail) { setDetailView(itemDetail); } @Override public void failure(int statusCode, Throwable error, JSONObject errorResponse) { // Τϥʔॲཧ } }); } android-async-http v2.0~
  43. • v1.0~ • v2.0~ • v3.5~

  44.  v4.0

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

  46. ։ൃ؀ڥ v3.5~ • GitHub • Android Studio • Gradle •

    CI (CircleCI)
  47. ΞʔΩςΫνϟ • Activity + Fragment • DB • Content Provider

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

  49. Observable<ItemDetail> observable = FrilServiceCreator.createFrilService(activity).getItemDetail(itemId);
 return observable.subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(new Action1<ItemDetail>() {


    @Override
 public void call(ItemDetail itemDetail) { setItemDetail(itemDetail);
 }
 }, new Action1<Throwable>() {
 @Override
 public void call(Throwable throwable) {
 Timber.e(throwable, throwable.getMessage());
 }
 }); Retrofit + RxJava v3.5~ @GET("/api/item")
 Observable<ItemDetail> getItemDetail(
 @Query("item_id") Integer itemId
 );
  50. ΞʔΩςΫνϟͷมભ

  51. ΞʔΩςΫνϟͷมભ    ωοτϫʔΫ "TZOD5BTL-PBEFS BOESPJE BTZODIUUQ 3FUSPpU ඇಉظॲཧ

    ΠϯλϑΣʔε -PBEFS $BMMCBDL 3Y+BWB &WFOU#VT TUBSU"DUJWJUZ'PS3FTVMU 0UUP 0UUP ը૾ 63-$POOFDUJPO 1JDBTTP 1JDBTTP
  52. RxJavaΛಋೖ͢Δ·Ͱ͸…

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

  54. ྫʣϚελσʔλͷߋ৽ॲཧ 1. Ϛελσʔλͷߋ৽ΛνΣοΫ
 2. ߋ৽͕͋Ε͹ඞཁͳ߲໨Λߋ৽͢Δ
 3. શͯͷσʔλͷߋ৽͕׬ྃͨ͠Βݺͼग़͠ݩʹ໭͢

  55. RxJavaಋೖલ public class MigrateMasterDataTask extends AsyncTask<Void, Void, Boolean> { @Override


    protected Boolean doInBackground(Void... params) { final FrilService frilService = FrilServiceCreator.createFrilService(context);
 
 //ϦϞʔτ͔ΒͷऔΓࠐΈ͕ඞཁ͔νΣοΫ
 Call<DatabaseUpdate> 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); } }
  56. RxJavaಋೖલ public class MigrateMasterDataTask extends AsyncTask<Void, Void, Boolean> { @Override


    protected Boolean doInBackground(Void... params) { final FrilService frilService = FrilServiceCreator.createFrilService(context);
 
 //ϦϞʔτ͔ΒͷऔΓࠐΈ͕ඞཁ͔νΣοΫ
 Call<DatabaseUpdate> 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); } }
  57. RxJavaಋೖલ public class MigrateMasterDataTask extends AsyncTask<Void, Void, Boolean> { @Override


    protected Boolean doInBackground(Void... params) { final FrilService frilService = FrilServiceCreator.createFrilService(context);
 
 //ϦϞʔτ͔ΒͷऔΓࠐΈ͕ඞཁ͔νΣοΫ
 Call<DatabaseUpdate> 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); } }
  58. RxJavaಋೖલ private boolean syncFromRemote(boolean isNeedUpdateBrand, boolean isNeedUpdateBank) { if (isNeedUpdateBrand)

    { // ωοτϫʔΫ͔ΒϒϥϯυϚελΛಉظऔಘ } if (isNeedUpdateBank) { // ωοτϫʔΫ͔ΒۜߦϚελΛಉظऔಘ } if (brandVersion != null) { // ΞϓϦ಺ʹ࠷৽ͷόʔδϣϯ৘ใΛอଘ } if (bankVersion != null) { // ΞϓϦ಺ʹ࠷৽ͷόʔδϣϯ৘ใΛอଘ } return true; }
  59. ॲཧͷྲྀΕ͕௥͍ͮΒ͍ https://www.flickr.com/photos/eughenes/3758142701

  60. RxJavaͷಋೖ

  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<Completable> 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);
  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<Completable> 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);
  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<Completable> 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);
  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<Completable> 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);
  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<Completable> 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);
  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<Completable> 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);
  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<Completable> 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);
  68. None
  69. ωοτϫʔΫϨΠϠͷ੔උʹΑ ΓɺॲཧͷྲྀΕ͕Θ͔Γ΍͘͢ ͳͬͨ

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

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

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

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

  74. ϑϦϧAndroid൛ͷ։ൃ • Issue؅ཧ • ϓϧϦΫΤετ • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ • CI •

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

    ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε࡞ۀ • ਺ࣈͷܭଌ
  76. Issue؅ཧ • LabelΛ׆༻ • ready͸ίʔυϨϏϡʔ ͯ͠OK

  77. ϑϦϧAndroid൛ͷ։ൃ • Issue؅ཧ • ϓϧϦΫΤετ • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ • CI •

    ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ਺ࣈͷܭଌ
  78. ϓϧϦΫΤετ • GitHubͷςϯϓϨʔτΛ׆༻ • ֬ೝखॱ͕ॏཁ

  79. ϑϦϧAndroid൛ͷ։ൃ • Issue؅ཧ • ϓϧϦΫΤετ • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ • CI •

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

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

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

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

  85. READMEͷ੔උ 

  86. JavaDocͷ੔උ

  87. JavaDocͷ੔උ

  88. @colorͷ੔උ

  89. @colorͷ੔උ

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

    to = 0xFF) int alpha) {
  91. AnnotationΛੵۃతʹ࢖͏

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

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

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

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

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

  97. ϑϦϧAndroid൛ͷ։ൃ • Issue؅ཧ • ϓϧϦΫΤετ • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ • CI •

    ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ਺ࣈͷܭଌ
  98. CI

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

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

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

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

  104. None
  105. ϑϦϧAndroid൛ͷ։ൃ • Issue؅ཧ • ϓϧϦΫΤετ • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ • CI •

    ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ਺ࣈͷܭଌ
  106. ϢʔβʔΠϯλϏϡʔ

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

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

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

  110. ϑϦϧAndroid൛ͷ։ൃ • Issue؅ཧ • ϓϧϦΫΤετ • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ • CI •

    ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ਺ࣈͷܭଌ
  111. QA • ΤϯδχΞɺCSελοϑɺσβΠφʔɺϓϩμΫ τΦʔφʔ͕ࢀՃ • ςετγʔτΛࣄલʹΤϯδχΞ͕࡞੒͠ɺ߲໨ ʹԊͬͯಈ࡞νΣοΫΛ͍ͯ͘͠ • ΞϓϦͷΫΦϦςΟνΣοΫ •

    ࢓༷֬ೝ΋݉Ͷ͍ͯΔ
  112. ςετγʔτ

  113. QAͷྲྀΕ ࣄલ४උ 1. git-pr-releaseͰϦϦʔεʹ޲͚ͯQA༻ͷϓϧϦΫΤετΛ࡞Δ 2. ΤϯδχΞ͕ϓϧϦΫΤετͷίϝϯτΛϕʔεʹQA։࢝·Ͱʹςετγʔτ Λهࡌ͢Δ 3. SlackͰQAΛґཔ ౰೔

    1. ςετͷ֓ཁΛઆ໌ 2. ςετγʔτʹ͕ͨͬͯ͠ςετΛ͍ͯ͘͠ 3. ؾ͍ͮͨ͜ͱ͕͋Ε͹શͯϝϞΛ࢒͢
  114. None
  115. ϑϦϧAndroid൛ͷ։ൃ • Issue؅ཧ • ϓϧϦΫΤετ • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ • CI •

    ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ਺ࣈͷܭଌ
  116. ϦϦʔε .git-pr-templateʹϦϦʔεखॱΛهࡌ

  117. None
  118. ϑϦϧAndroid൛ͷ։ൃ • Issue؅ཧ • ϓϧϦΫΤετ • ίʔσΟϯάن໿ɺυΩϡϝϯτͷ੔උ • CI •

    ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ਺ࣈͷܭଌ
  119. ΞϓϦ಺Πϕϯτͷܭଌ https://www.flickr.com/photos/helenanormark/9421984744

  120. ΞϓϦ಺Πϕϯτͷܭଌ • Google Analytics • Facebook Analytics • Fabric •

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

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

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

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

  125. ໨తผνʔϜԽ

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

    ♂♂♂ ♂♂♂
  127. ৽ػೳ΋௥Ճ͍͚ͨ͠Ͳɺ
 Android MରԠ͠ͳ͍ͱ…

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

    • σβΠϯҕһձ • ෼ੳҕһձ
  129. ໨తผνʔϜ B A ♂♂♂♂ ♂ ♂♂♂ ♂ ♂ ♂♂♂♂ ♂

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

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

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

    • ରԠ಺༰͸Ͱ͖ΔݶΓwikiʹ࢒͢
  133. αϙʔτରԠͷ౰൪੍ • ஌ࣝͷଐਓԽ͕ݮͬͨ • αʔϏε࢓༷ͷཧղ͕ਂ·ͬͨ • ྫ͑͹औҾपΓͷ࢓༷ͳͲ

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

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

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

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

  140. 

  141. ·ͱΊ

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

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