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.9k
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
200
Androidアプリ開発における技術顧問としての役割 #DroidKaigi 2018
cutmail
1
2.4k
フリルにおけるドッグフーディング / Fashion Tech Meetup #2 LT
cutmail
2
3.9k
Adapter and Custom Layout
cutmail
3
920
いかにして不具合発見時の フィードバックを素早く行うか #potatotips 12
cutmail
0
2.5k
Androidのログ出力をいい感じにする #potatotips 9
cutmail
8
9.8k
コーディング規約を緩く守りつつ仕事の成果を出す方法
cutmail
2
620
Other Decks in Technology
See All in Technology
An introduction to Claude Code SDK
choplin
3
3.1k
Figma Dev Mode MCP Serverを用いたUI開発
zoothezoo
1
290
PdM業務における使い分け
shinshiro
0
360
Contract One Engineering Unit 紹介資料
sansan33
PRO
0
7k
振り返りTransit Gateway ~VPCをいい感じでつなげるために~
masakiokuda
4
230
セキュアなAI活用のためのLiteLLMの可能性
tk3fftk
1
570
Microsoft Fabric ガバナンス設計の一歩目を考える
ryomaru0825
1
190
「現場で活躍するAIエージェント」を実現するチームと開発プロセス
tkikuchi1002
6
930
安定した基盤システムのためのライブラリ選定
kakehashi
PRO
3
160
研究開発部メンバーの働き⽅ / Sansan R&D Profile
sansan33
PRO
3
18k
Ktor + Google Cloud Tasks/PubSub におけるOTel Messaging計装の実践
sansantech
PRO
1
180
LLM拡張解体新書/llm-extension-deep-dive
oracle4engineer
PRO
27
7.8k
Featured
See All Featured
Embracing the Ebb and Flow
colly
86
4.8k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
50k
Code Review Best Practice
trishagee
69
19k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.8k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
50
5.5k
What's in a price? How to price your products and services
michaelherold
246
12k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
How GitHub (no longer) Works
holman
314
140k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
Designing Experiences People Love
moore
142
24k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
34
3.1k
RailsConf 2023
tenderlove
30
1.2k
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ϢʔβʔΠϯλϏϡʔͳ ͲͰ࣮ࡍͷϢʔβʔͷΛฉ͖ɺαʔϏεʹ ө͢Δ
͝੩ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠