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
5k
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
220
Androidアプリ開発における技術顧問としての役割 #DroidKaigi 2018
cutmail
1
2.4k
フリルにおけるドッグフーディング / Fashion Tech Meetup #2 LT
cutmail
2
4k
Adapter and Custom Layout
cutmail
3
960
いかにして不具合発見時の フィードバックを素早く行うか #potatotips 12
cutmail
0
2.6k
Androidのログ出力をいい感じにする #potatotips 9
cutmail
8
9.9k
コーディング規約を緩く守りつつ仕事の成果を出す方法
cutmail
2
650
Other Decks in Technology
See All in Technology
Oracle Database@Azure:サービス概要のご紹介
oracle4engineer
PRO
3
320
_第4回__AIxIoTビジネス共創ラボ紹介資料_20251203.pdf
iotcomjpadmin
0
180
20251225_たのしい出張報告&IgniteRecap!
ponponmikankan
0
110
「アウトプット脳からユーザー価値脳へ」がそんなに簡単にできたら苦労しない #RSGT2026
aki_iinuma
11
4.8k
Digitization部 紹介資料
sansan33
PRO
1
6.5k
わが10年の叡智をぶつけたカオスなクラウドインフラが、なくなるということ。
sogaoh
PRO
1
440
困ったCSVファイルの話
mottyzzz
0
100
善意の活動は、なぜ続かなくなるのか ーふりかえりが"構造を変える判断"になった半年間ー
matsukurou
0
420
「駆動」って言葉、なんかカッコイイ_Mitz
comucal
PRO
0
140
形式手法特論:コンパイラの「正しさ」は証明できるか? #burikaigi / BuriKaigi 2026
ytaka23
16
4.9k
Contract One Engineering Unit 紹介資料
sansan33
PRO
0
12k
Qiita Bash アドカレ LT #1
okaru
0
180
Featured
See All Featured
Crafting Experiences
bethany
0
28
Designing for humans not robots
tammielis
254
26k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
39
Exploring anti-patterns in Rails
aemeredith
2
220
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
35
Believing is Seeing
oripsolob
1
26
The Curious Case for Waylosing
cassininazir
0
200
How to make the Groovebox
asonas
2
1.9k
Raft: Consensus for Rubyists
vanstee
141
7.3k
Code Review Best Practice
trishagee
74
19k
Why You Should Never Use an ORM
jnunemaker
PRO
61
9.7k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
51
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ϢʔβʔΠϯλϏϡʔͳ ͲͰ࣮ࡍͷϢʔβʔͷΛฉ͖ɺαʔϏεʹ ө͢Δ
͝੩ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠