Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
4年続くアプリにおけるチーム開発 #DroidKaigi 2017
Tatsuya Arai
March 10, 2017
Technology
13
4.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
120
Androidアプリ開発における技術顧問としての役割 #DroidKaigi 2018
cutmail
1
2.1k
フリルにおけるドッグフーディング / Fashion Tech Meetup #2 LT
cutmail
2
3.5k
Adapter and Custom Layout
cutmail
3
750
いかにして不具合発見時の フィードバックを素早く行うか #potatotips 12
cutmail
0
2.2k
Androidのログ出力をいい感じにする #potatotips 9
cutmail
8
8.4k
コーディング規約を緩く守りつつ仕事の成果を出す方法
cutmail
2
510
Other Decks in Technology
See All in Technology
Goで実装するブランドネットワークとの接続ポイント
pongzu
2
270
合同IT企業説明会から学ぶエンジニア向けの広報戦略
nagutabby
1
230
Settlement simulation testing to ensure correct settlement processing
applepine1125
2
1.2k
セキュアなTerraformの使い方 ~ 機密情報をコードに含めず環境構築するにはどうしたらいいの?
harukasakihara
9
1.5k
SPAとWebアプリケーションでCognitoの使い方はどう変わるのか? / How do we use cognito with SPA and web applications?
kitano_yuichi
0
390
ここが好きだよAWS管理ポリシー_devio2022/i_am_iam_lover
yukihirochiba
0
3.2k
AWS CLI でやってみる ~ AWS Hands-on for Beginners ECS ハンズオン ~
kentosuzuki
1
480
AWSを使う上で意識しておきたい、クラウドセキュリティ超入門(駆け足版)
kkmory
0
150
SBOMを利用したソフトウェアサプライチェーンの保護
masahiro331
1
190
私のAWS愛を聞け!ここが好きだよAmazon FSx for NetApp ONTAP
non97
0
760
データ分析のためのAWS Well-Architected -Data Analytics Lens-
maru1981
0
230
JAWS-UG 朝会 #36 登壇資料
takakuni
1
550
Featured
See All Featured
Mobile First: as difficult as doing things right
swwweet
213
7.6k
Making the Leap to Tech Lead
cromwellryan
113
7.4k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
236
1.1M
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
119
28k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
21
1.4k
GraphQLとの向き合い方2022年版
quramy
16
8.4k
How to train your dragon (web standard)
notwaldorf
60
3.9k
YesSQL, Process and Tooling at Scale
rocio
157
12k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
107
16k
Infographics Made Easy
chrislema
233
17k
Code Reviewing Like a Champion
maltzj
506
37k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
349
27k
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ϢʔβʔΠϯλϏϡʔͳ ͲͰ࣮ࡍͷϢʔβʔͷΛฉ͖ɺαʔϏεʹ ө͢Δ
͝੩ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠