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
210
Androidアプリ開発における技術顧問としての役割 #DroidKaigi 2018
cutmail
1
2.4k
フリルにおけるドッグフーディング / Fashion Tech Meetup #2 LT
cutmail
2
3.9k
Adapter and Custom Layout
cutmail
3
950
いかにして不具合発見時の フィードバックを素早く行うか #potatotips 12
cutmail
0
2.5k
Androidのログ出力をいい感じにする #potatotips 9
cutmail
8
9.9k
コーディング規約を緩く守りつつ仕事の成果を出す方法
cutmail
2
640
Other Decks in Technology
See All in Technology
プログラミング言語を書く前に日本語を書く── AI 時代に求められる「言葉で考える」力/登壇資料(井田 献一朗)
hacobu
PRO
0
150
メタプログラミングRuby問題集の活用
willnet
2
770
Spring Boot利用を前提としたJavaライブラリ開発方法の提案
kokihoshihara
PRO
2
130
Proxmox × HCP Terraformで始めるお家プライベートクラウド
lamaglama39
1
190
AWS IAM Identity Centerによる権限設定をグラフ構造で可視化+グラフRAGへの挑戦
ykimi
2
720
3年ぶりの re:Invent 今年の意気込みと前回の振り返り
kazzpapa3
0
200
[JDDStudy #10] 社内Agent勉強会の取り組み紹介
yp_genzitsu
1
130
設計は最強のプロンプト - AI時代に武器にすべきスキルとは?-
kenichirokimura
1
350
【AWS reInvent 2025 関西組 事前勉強会】re:Inventの“感動と興奮”を思い出してモチベ爆上げしたいです
ttelltte
0
140
Rubyist入門: The Way to The Timeless Way of Programming
snoozer05
PRO
5
310
やり方は一つだけじゃない、正解だけを目指さず寄り道やその先まで自分流に楽しむ趣味プログラミングの探求 2025-11-15 YAPC::Fukuoka
sugyan
1
350
AI時代に必要なデータプラットフォームの要件とは by @Kazaneya_PR / 20251107
kazaneya
PRO
4
970
Featured
See All Featured
Designing Experiences People Love
moore
142
24k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
46
7.8k
Producing Creativity
orderedlist
PRO
348
40k
Embracing the Ebb and Flow
colly
88
4.9k
Statistics for Hackers
jakevdp
799
220k
It's Worth the Effort
3n
187
28k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
8k
4 Signs Your Business is Dying
shpigford
186
22k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.7k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.3k
Why Our Code Smells
bkeepers
PRO
340
57k
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ϢʔβʔΠϯλϏϡʔͳ ͲͰ࣮ࡍͷϢʔβʔͷΛฉ͖ɺαʔϏεʹ ө͢Δ
͝੩ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠