Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
4年続くアプリにおけるチーム開発 #DroidKaigi 2017
Search
Tatsuya Arai
March 10, 2017
Technology
13
4.7k
4年続くアプリにおけるチーム開発 #DroidKaigi 2017
2017/3/10
#DroidKaigi 2017 でお話した「フリル」のチーム開発に関する資料です
Tatsuya Arai
March 10, 2017
Tweet
Share
More Decks by Tatsuya Arai
See All by Tatsuya Arai
5 minutes PWA
cutmail
0
160
Androidアプリ開発における技術顧問としての役割 #DroidKaigi 2018
cutmail
1
2.2k
フリルにおけるドッグフーディング / Fashion Tech Meetup #2 LT
cutmail
2
3.7k
Adapter and Custom Layout
cutmail
3
810
いかにして不具合発見時の フィードバックを素早く行うか #potatotips 12
cutmail
0
2.4k
Androidのログ出力をいい感じにする #potatotips 9
cutmail
8
9.3k
コーディング規約を緩く守りつつ仕事の成果を出す方法
cutmail
2
550
Other Decks in Technology
See All in Technology
ユーザーストーリーのレビューを自動化したみたの
bun913
1
410
Postman v10リリース後を振り返る
nagix
0
170
長期運用プロジェクトでのMySQLからTiDB移行の検証
colopl
2
830
web-application-security
matsuihidetoshi
0
140
GraphQL 成熟度モデルの紹介と、プロダクトに当てはめた事例 / GraphQL maturity model
mh4gf
7
1.3k
ServiceNow Knowledge 24の歩き方 EYストラテジー・アンド・コンサルティング
manarobot
0
180
TechFeed Experts Night#27 〜 フロントエンドフレームワーク最前線 (Svelte)
baseballyama
1
290
複雑な構成要素を持つUIとの向き合い方 〜新・支出グラフでの実例〜 / B43 TECH TALK
nakamuuu
0
140
AOAI をきっかけに 社内の Azure 管理を見直した話
recruitengineers
PRO
1
260
JSON攻略法.pdf
miyakemito
8
4.9k
自己改善からチームを動かす! 「セルフエンジニアリングマネージャー」のすゝめ
shoota
6
270
Azure犬駆動開発の記録/GlobalAzureFukuoka2024_20240420
nina01
1
200
Featured
See All Featured
Why Our Code Smells
bkeepers
PRO
331
56k
What the flash - Photography Introduction
edds
64
11k
Fashionably flexible responsive web design (full day workshop)
malarkey
398
65k
Six Lessons from altMBA
skipperchong
21
3k
How to name files
jennybc
65
93k
Documentation Writing (for coders)
carmenintech
60
3.9k
Scaling GitHub
holman
457
140k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
357
22k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
78
42k
Bootstrapping a Software Product
garrettdimon
PRO
302
110k
Code Reviewing Like a Champion
maltzj
514
39k
[RailsConf 2023] Rails as a piece of cake
palkan
23
3.9k
Transcript
4ଓ͘ΞϓϦʹ͓͚Δ νʔϜ։ൃ @cutmail ϋογϡλάɿ#DroidKaigi2
ରऀ • AndroidΞϓϦͷνʔϜ։ൃΛ͍ͯ͠Δ • AndroidΞϓϦͷνʔϜ։ൃʹڵຯ͕͋Δ • AndroidΞϓϦͷνʔϜ։ൃΛ͍ͯͯ͠Կ͔ ࠔ͍ͬͯΔ
ΞδΣϯμ • ϑϦϧAndroid൛ͷي • ϑϦϧʹ͓͚Δ։ൃ • νʔϜ։ൃΛ͏·͘ճͨ͢Ίʹ͖ͬͯͨ͜ ͱ
@cutmail • גࣜձࣾFablic Co-Founder/Engineer • Android / iOS / Ruby
on Rails
None
ϑϦϧ
None
ϑϦϧ • ຊॳͷϑϦϚΞϓϦ • ॳঁੑݶఆɺͷͪʹஉੑʹղ์ • 20127݄ʹiOS൛͕ϦϦʔε
ϑϦϧ for iOS • ࣌Titanium MobileͰ։ൃ։࢝ • ։ൃظؒ3ϲ݄ • ͷͪʹ༷ʑͳࣄͰωΠςΟϒԽ
͓΅͍͑ͯ·͔͢ Titanium Mobile
Titanium Mobile • ࣌JavascriptͰΫϩεϓϥοτϑΥʔϜ։ ൃ͕Ͱ͖ΔͱݴΘΕ͍ͯͨ • ࣌ͷTitanium MobileͰͷAndroidΞϓϦ։ ൃ
ϑϦϧ for Android Java!
ϑϦϧ for Android • 201211݄6 ॳgitίϛοτ • 20131݄29 v1.0ϦϦʔε •
࠷ۙKotlin͕ಋೖ͞Ε·ͨ͠
v1.0
Լλϒ!!!
None
• ϑϦϧAndroid൛ͷي • ϑϦϧʹ͓͚Δ։ൃ • νʔϜ։ൃΛ͏·͘ճͨ͢Ίʹ͖ͬͯͨ͜ ͱ ΞδΣϯμ
• 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ೝূ
• 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ೝূ
ΞϓϦΞΠίϯͷมԽ
ϑϦϧͷྺ࢙ ϦχϡʔΞϧͷྺ࢙
ϦχϡʔΞϧ༷ʑͳԠ͕ ى͖Δ
https://speakerdeck.com/shoby/yuzanishou-keru-rerare-wen-ti-woqi-kosiduraida-gui-mo-riniyuarufalsejin-mefang
• v1.0~ • v2.0~ • v3.5~
։ൃମ੍ • AndroidΞϓϦΤϯδχΞ 1.5໊ • ࣗiOSΛΓͳ͕ΒยखؒAndroid • σβΠφʔ 1໊ v1.0~v1.1
♂♂
։ൃڥ • Eclipse • Bitbucket « v1.0~v1.1
։ൃϑϩʔ • ϓϧϦΫΤετɺίʔυϨϏϡʔͳ͠ • developϒϥϯν͔ΒͦΕͧΕϒϥϯνΛͬ ֤ͯࣗͰϚʔδ • جຊతʹiOSͷػೳΛͦͷ··Ҡ২ v1.0~v1.1
None
None
ΞʔΩςΫνϟ • Activity • DB • Content Provider • ը૾ಡΈࠐΈ
• URLConnectionʹΑΔࣗલ࣮ v1.0~v1.1
ΞʔΩςΫνϟ • API • AsyncTaskLoaderϕʔε • ը໘ؒͷΠϕϯτ௨ • startActivityForResult •
্෦ͷόʔࣗલ v1.0~v1.1
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Ϩεϙϯε
• v1.0~v1.1 • v2.0~ • v3.5~
v2.3
v3.0
ମ੍ • AndroidΞϓϦΤϯδχΞ 5໊ • σβΠφʔ 1໊ v2.0~v3.4 ♂♂♂ ♂♂
։ൃڥ • GitHub • Android Studio • Gradle • CI
(Travis-CI) v2.0~
։ൃϑϩʔ • ϓϧϦΫΤετಋೖ • ίʔυϨϏϡʔಋೖ v2.0~
ΞʔΩςΫνϟ • Activity + Fragment • ԣը໘ɺλϒϨοτରԠͷͨΊʹFragmentͷಋೖ • DB •
Content Provider • ը૾ಡΈࠐΈ • Picasso v2.0~
ΞʔΩςΫνϟ • API • android-async-http • EventBus • Otto v2.0~
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~
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~
• v1.0~ • v2.0~ • v3.5~
v4.0
։ൃମ੍ • AndroidΞϓϦΤϯδχΞ 2໊ • σβΠφʔ 1໊ v3.5~ ♂♂
։ൃڥ v3.5~ • GitHub • Android Studio • Gradle •
CI (CircleCI)
ΞʔΩςΫνϟ • Activity + Fragment • DB • Content Provider
v3.5~
ΞʔΩςΫνϟ • API • Retrofit + RxJava v3.5~
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 );
ΞʔΩςΫνϟͷมભ
ΞʔΩςΫνϟͷมભ ωοτϫʔΫ "TZOD5BTL-PBEFS BOESPJE BTZODIUUQ 3FUSPpU ඇಉظॲཧ
ΠϯλϑΣʔε -PBEFS $BMMCBDL 3Y+BWB &WFOU#VT TUBSU"DUJWJUZ'PS3FTVMU 0UUP 0UUP ը૾ 63-$POOFDUJPO 1JDBTTP 1JDBTTP
RxJavaΛಋೖ͢Δ·Ͱ…
ྫ͑ɺΞϓϦͷϚελσʔλ ʢۜߦɺϒϥϯυʣ ͷߋ৽
ྫʣϚελσʔλͷߋ৽ॲཧ 1. Ϛελσʔλͷߋ৽ΛνΣοΫ 2. ߋ৽͕͋Εඞཁͳ߲Λߋ৽͢Δ 3. શͯͷσʔλͷߋ৽͕ྃͨ͠Βݺͼग़͠ݩʹ͢
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); } }
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); } }
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); } }
RxJavaಋೖલ private boolean syncFromRemote(boolean isNeedUpdateBrand, boolean isNeedUpdateBank) { if (isNeedUpdateBrand)
{ // ωοτϫʔΫ͔ΒϒϥϯυϚελΛಉظऔಘ } if (isNeedUpdateBank) { // ωοτϫʔΫ͔ΒۜߦϚελΛಉظऔಘ } if (brandVersion != null) { // ΞϓϦʹ࠷৽ͷόʔδϣϯใΛอଘ } if (bankVersion != null) { // ΞϓϦʹ࠷৽ͷόʔδϣϯใΛอଘ } return true; }
ॲཧͷྲྀΕ͕͍ͮΒ͍ https://www.flickr.com/photos/eughenes/3758142701
RxJavaͷಋೖ
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);
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);
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);
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);
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);
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);
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);
None
ωοτϫʔΫϨΠϠͷඋʹΑ ΓɺॲཧͷྲྀΕ͕Θ͔Γ͘͢ ͳͬͨ
RetrofitʹΑΓΤϯυϙΠϯτఆ ٛͷίʔυ͕গͳ͘ͳͬͨ
ΞʔΩςΫνϟυΩϡϝϯτ ͷඋ͕νʔϜͷ։ൃʹӨ ڹ͢Δ
͜͜·Ͱ͕ϑϦϧAndroid൛ ͷي
• ϑϦϧAndroid൛ͷي • ϑϦϧʹ͓͚Δ։ൃ • νʔϜ։ൃΛ͏·͘ճͨ͢Ίʹ͖ͬͯͨ͜ ͱɺͬͯྑ͔ͬͨ͜ͱ ΞδΣϯμ
ϑϦϧAndroid൛ͷ։ൃ • Issueཧ • ϓϧϦΫΤετ • ίʔσΟϯάنɺυΩϡϝϯτͷඋ • CI •
ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε࡞ۀ • ࣈͷܭଌ
ϑϦϧAndroid൛ͷ։ൃ • Issueཧ • ϓϧϦΫΤετ • ίʔσΟϯάنɺυΩϡϝϯτͷඋ • CI •
ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε࡞ۀ • ࣈͷܭଌ
Issueཧ • LabelΛ׆༻ • readyίʔυϨϏϡʔ ͯ͠OK
ϑϦϧAndroid൛ͷ։ൃ • Issueཧ • ϓϧϦΫΤετ • ίʔσΟϯάنɺυΩϡϝϯτͷඋ • CI •
ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ࣈͷܭଌ
ϓϧϦΫΤετ • GitHubͷςϯϓϨʔτΛ׆༻ • ֬ೝखॱ͕ॏཁ
ϑϦϧAndroid൛ͷ։ൃ • Issueཧ • ϓϧϦΫΤετ • ίʔσΟϯάنɺυΩϡϝϯτͷඋ • CI •
ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ࣈͷܭଌ
ίʔσΟϯάنͱυΩϡϝϯτͷඋ • ίʔσΟϯάنΛఆΊ͍ͯ·͢ • CONTRIBUTING.md • ϓϧϦΫΤετΛग़͢ࡍʹϦϯΫ͕දࣔ͞ ΕΔ
None
جຊతʹCookpadͷ ίʔσΟϯάنʹ४ڌ͢Δ
https://github.com/cookpad/styleguide
ࡉ͔͍ͱ͜ΖͰ໎Θͳ͍Α͏ ʹίʔσΟϯάنΛඋ
READMEͷඋ
JavaDocͷඋ
JavaDocͷඋ
@colorͷඋ
@colorͷඋ
AnnotationΛੵۃతʹ͏ dependencies { compile 'com.android.support:support-annotations:24.2.0' } void setActionBarAlpha(@IntRange(from = 0x0,
to = 0xFF) int alpha) {
AnnotationΛੵۃతʹ͏
https://developer.android.com/studio/write/ annotations.html Improve Code Inspection with Annotations
ϝϯςφϯε͍͢͠ঢ়ଶʹ ͓ͯ͘͠
։ൃ͕εϜʔζʹਐΈग़͢ͱ ى͖Δͷ͕
ϓϧϦΫΤετཷ·Δ
ϓϧϦΫΤετཷ·Δ • ཷ·͍ͬͯΔϓϧϦΫΤετΛSlackʹ௨ • ϨϏϡʔ͠ͳ͍ͱຖͲΜͲΜ૿͍͑ͯ͘
ϑϦϧAndroid൛ͷ։ൃ • Issueཧ • ϓϧϦΫΤετ • ίʔσΟϯάنɺυΩϡϝϯτͷඋ • CI •
ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ࣈͷܭଌ
CI
http://in.fablic.co.jp/entry/circle-ci-in-android-project
None
ϕʔλ൛ͷࣾ • Fabric betaͰࣾϢʔβʔʹ • developϒϥϯνʹmerge͞ΕΔʹ࠷৽൛͕ ͞ΕΔ
Fabric betaʹΑΔβ൛ webhook push
ϦϦʔε൛ͱผΞϓϦͱͯ͠
None
ϑϦϧAndroid൛ͷ։ൃ • Issueཧ • ϓϧϦΫΤετ • ίʔσΟϯάنɺυΩϡϝϯτͷඋ • CI •
ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ࣈͷܭଌ
ϢʔβʔΠϯλϏϡʔ
ϢʔβʔΠϯλϏϡʔ • iOS൛ϦϦʔεͷࡍʹ100ਓ͘Β͍Ϣʔβʔ Λั·͑ͯΠϯλϏϡʔΛ࣮ࢪ • ϑϦϧϢʔβʔʹฉ͘จԽ
ϢʔβʔΠϯλϏϡʔ • ຖճͰͳ͍͕େ͖ͳϦχϡʔΞϧͳͲͷࡍ ߦ͏ • ࣾͷCSελοϑશһϑϦϧϢʔβʔͷͨ Ίɺ͙͢ʹΠϯλϏϡʔ͕Մೳ • ελϯσΟϯάσεΫͰΧδϡΞϧʹΠϯλ Ϗϡʔ
ϢʔβʔΠϯλϏϡʔ • QAલޙʹΠϯλϏϡʔΛͨ͠Γ͢Δ • ΠϯλϏϡʔͷ݁Ռɺ༷Λม͑Δ͜ͱ͋ Δ • ੈʹग़Δલʹ࣮ࡍͷϢʔβʔͷ͕ฉ͚Δͨ ΊΤϯδχΞͱͯ҆͠৺
ϑϦϧAndroid൛ͷ։ൃ • Issueཧ • ϓϧϦΫΤετ • ίʔσΟϯάنɺυΩϡϝϯτͷඋ • CI •
ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ࣈͷܭଌ
QA • ΤϯδχΞɺCSελοϑɺσβΠφʔɺϓϩμΫ τΦʔφʔ͕ࢀՃ • ςετγʔτΛࣄલʹΤϯδχΞ͕࡞͠ɺ߲ ʹԊͬͯಈ࡞νΣοΫΛ͍ͯ͘͠ • ΞϓϦͷΫΦϦςΟνΣοΫ •
༷֬ೝ݉Ͷ͍ͯΔ
ςετγʔτ
QAͷྲྀΕ ࣄલ४උ 1. git-pr-releaseͰϦϦʔεʹ͚ͯQA༻ͷϓϧϦΫΤετΛ࡞Δ 2. ΤϯδχΞ͕ϓϧϦΫΤετͷίϝϯτΛϕʔεʹQA։࢝·Ͱʹςετγʔτ Λهࡌ͢Δ 3. SlackͰQAΛґཔ
1. ςετͷ֓ཁΛઆ໌ 2. ςετγʔτʹ͕ͨͬͯ͠ςετΛ͍ͯ͘͠ 3. ؾ͍ͮͨ͜ͱ͕͋ΕશͯϝϞΛ͢
None
ϑϦϧAndroid൛ͷ։ൃ • Issueཧ • ϓϧϦΫΤετ • ίʔσΟϯάنɺυΩϡϝϯτͷඋ • CI •
ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ࣈͷܭଌ
ϦϦʔε .git-pr-templateʹϦϦʔεखॱΛهࡌ
None
ϑϦϧAndroid൛ͷ։ൃ • Issueཧ • ϓϧϦΫΤετ • ίʔσΟϯάنɺυΩϡϝϯτͷඋ • CI •
ϢʔβʔΠϯλϏϡʔ • QA • ϦϦʔε • ࣈͷܭଌ
ΞϓϦΠϕϯτͷܭଌ https://www.flickr.com/photos/helenanormark/9421984744
ΞϓϦΠϕϯτͷܭଌ • Google Analytics • Facebook Analytics • Fabric •
Firebase→BigQuery • ࠂSDK
ෳͷSDKΛೖΕ͍ͯΔཧ༝ • ֤αʔϏεʹΑͬͯݟ͑Δͷ͕ҧͬͨΓɺࢪ ࡦΛߟ͑ΔࡍʹݟΔࣈͷ͕֯ҧͬͨΓ͢Δ • Ұ͚͕ͭͩͣΕͨΓͯ͠ݕ͢Δ͜ͱ͕ Ͱ͖Δ • αʔϏε্ॏཁͳKPIಠࣗͷཧը໘Λ࡞ͬ ͯΥον
εΫϦʔϯΠϕϯτͷܭଌ • ΤϯδχΞͷख࡞ۀͰ֤Activityʹܭଌίʔυ ΛຒΊࠐΜͰ͍Δ • ͔ͭͯiOSͰػցతʹຒΊࠐΉ͜ͱΛ͕ͨ͠ɺ ूܭ݁Ռ͕ͪ͝Όͪ͝Όʹͳͬͯ͠·ͬͨ͜ ͱ͕͋ͬͨ
εΫϦʔϯΠϕϯτͷॏཁੑ • ػೳΛվળͨ͠Γɺ͢ɺ͞ͳ͍ͷٞΛ ͢Δͱ͖ͷࡐྉʹͳΔ • ࣈΛϕʔεʹٞΛ͢Δ
• ϑϦϧAndroid൛ͷي • ීஈͷ։ൃͷྲྀΕ • νʔϜ։ൃΛ͏·͘ճͨ͢Ίʹͬͯྑ͔ͬ ͨ͜ͱ ΞδΣϯμ
తผνʔϜԽ
~2015ࠒ·Ͱ • iOSνʔϜɺAndroidνʔϜɺServerνʔϜɺ σβΠφʔͷΑ͏ͳઐ৬ͰΘ͔Ε͍ͯͨ iOS Android Server ♂♂♂ Design ♂♂♂
♂♂♂ ♂♂♂
৽ػೳՃ͍͚ͨ͠Ͳɺ Android MରԠ͠ͳ͍ͱ…
2015ࠒ~ • AνʔϜɺBνʔϜͱ͍͏తผͷνʔϜʹશͯͷ։ൃϝϯόʔ͕ ॴଐ • ҕһձ੍ • ΫϥΠΞϯτҕһձ • αʔόʔҕһձ
• σβΠϯҕһձ • ੳҕһձ
తผνʔϜ B A ♂♂♂♂ ♂ ♂♂♂ ♂ ♂ ♂♂♂♂ ♂
♂♂♂ ♂ ♂♂♂ ♂♂ iOS Android Server Design
తผνʔϜԽ • KPIผͷνʔϜʹͨ͜͠ͱͰඪ͕໌֬ʹͳͬ ͨ • Android͚ͩͱ͔Ͱͳ͘ɺඪΛୡ͢Δ ͨΊʹ෯͍εΩϧΛٻΊΒΕΔΑ͏ʹͳͬ ͨ
తผνʔϜԽ • ٕज़త՝ͳͲΛҕһձ͕Λ࣋ͭ͜ͱͰɺ ൣғ͕ΑΓ໌֬ʹ • ྫʣAndroid 6.0ରԠɺRailsΞοϓσʔτ
αϙʔτରԠͷ൪੍ • CS͔Β࣭ͳͲ͕͋ͬͨΒΘ͔Δਓ͕͍͑ͯͨ • 2016ΑΓσΠϦʔͷ൪੍Λಋೖ • ຖேbot͕2໊બͼɺબΕͨਓͦͷҰαϙʔτ͔Β ͷ࣭ʹ͑Δ • Θ͔Βͳ͍߹Θ͔ΔਓʹΤεΫϩʔ
• ରԠ༰Ͱ͖ΔݶΓwikiʹ͢
αϙʔτରԠͷ൪੍ • ࣝͷଐਓԽ͕ݮͬͨ • αʔϏε༷ͷཧղ͕ਂ·ͬͨ • ྫ͑औҾपΓͷ༷ͳͲ
ϦϦʔεϊʔτΛΤϯδχΞ͕ॻ͘
ϦϦʔεϊʔτΛΤϯδχΞ͕ॻ͘ • ࣮Λ୲ͨ͠ΤϯδχΞΛத৺ͱͯ͠ϦϦʔ εϊʔτΛॻ͘ • ਓؒຯͷ͋Δจষ • ࠷ऴతʹϥΠςΟϯάελοϑʹϨϏϡʔ͠ ͯΒ͏
PlayετΞͷϨϏϡʔ • AppFollowͱ͍͏αʔϏεΛಋೖ
None
None
PlayετΞͷϨϏϡʔ • Slackͷνϟϯωϧʹਵ࣌ඈΜͰ͘Δ • ωΨςΟϒͳϨϏϡʔ͕͋Ε։ൃɺCSνʔϜ͕ रͬͯվળʹཱͯΔ • CSνʔϜʹϨϏϡʔʹฦ৴͢Δ୲Λஔ͍͍ͯΔ • ΞϓϦͷධՁΛྑ͍ঢ়ଶʹอͭ
·ͱΊ
͘ଓ͘ΞϓϦΛ։ൃ͠ଓ͚Δ ʹ • ఆظతʹΞʔΩςΫνϟΛݟ͢ • نυΩϡϝϯτΛඋ͢Δ • ૾Ͱͳ͘ɺQAϢʔβʔΠϯλϏϡʔͳ ͲͰ࣮ࡍͷϢʔβʔͷΛฉ͖ɺαʔϏεʹ ө͢Δ
͝੩ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠