2017/05/09 shibuya.apk #14
LithoΛͬͯΈͨ2017/05/09 shibuya.apk #14
View Slide
sample Apphttps://github.com/ymnder/BLite2
whoami• Ryo Yamazaki / Nikkei Application Engineer• twitter:@ymnd• github:@ymnder• google i/o ߦ͖·͢3
product4
ࠓͷ͓ॻ͖ᶃLithoͱᶄLithoͰॻ͍ͯΈͨᶅͬͯΈͯͲ͏͔ͩͬͨ5
Lithoͱ• facebookͷUI framework ʢv0.2.0ʣminSDK 15• ReactͱComponentKitʹΠϯεύΠΞ͞Ε͍ͯΔ• RecyclerViewʹج͍ͮͨෳࡶ͔ͭεΫϩʔϥϒϧͳUIΛ࣮͢ΔͨΊʹͭ͘ΒΕͨ6
Lithoͷڧ͍ͱ͜Ζ• RecyclerView• 1000ms / 60fps ≒ 16ms• όΠϯυɾଌఆɾϨΠΞτ·ͰΛߦ͏ඞཁ• FacebookͷNewsFeedͷΑ͏ͳͨ͘͞ΜͷViewType͕ఆ͞ΕΔ߹ʹޮՌΛൃش͢Δ7
LithoͷΞʔΩςΫνϟᶃDeclarativeᶄAsynchronous layoutᶅFlatter view hierarchiesᶆFine-grained recycling8
ᶃDeclarative: એݴܕ• UIίϯϙʔωϯτΛఆٛ͢ΔએݴܕAPIΛ༻͢Δ• immutableͳೖྗʹج͖ͮUIͷϨΠΞτΛهड़͢Δ• code genarationͰίʔυΛγϯϓϧ͔ͭ؆୯ʹอͯΔ9
ᶄAsynchronous layout: ඇಉظϨΠΞτ• UIεϨουΛϒϩοΫͤͣʹࣄલʹUIΛଌఆ͠ϨΠΞτͰ͖Δ10
ᶅFlatter view hierarchies: ϑϥοτͳViewHierarchy• ϨΠΞτʹYogaΛ༻͠ɺࣗಈతʹUIؚ͕ΉViewGroupsΛݮΒ͢11
ᶅFlatter view hierarchies: ϑϥοτͳViewHierarchy• දࣔཁૉ͕ҰຕͷϑϥοτͳଘࡏʹͳΔ• ࣮ߦ࣌ʹViewLithoViewԼͷComponentHostʹू͞ΕΔ12
ConstraintLayoutͱͷࠩҟ• ྆ํͱViewͷ֊Λϑϥοτʹͯ͘͠ΕΔ• LithoFlexboxΛ࣮ݱ͢ΔYogaΛ༻͢Δ• xmlͰͳ࣮ͯ͘͠Ή• දࣔ࣌ʹTextViewͳͲͷཁૉΛશ෦̍ຕʹ·ͱΊΔ13
ᶆFine-grained recycling: ͖Ίࡉ͔͍ϦαΠΫϧ• ֤ςΩετɾը૾ɾө૾ͷΑ͏ͳUIΞΠςϜݸผʹϦαΠΫϧ͞ΕΔ• RecyclerViewͱҟͳΓɺListItem୯ҐͰ࠶ར༻͞ΕͣɺListItemͷதͷཁૉ୯ҐͰ࠶༻͞ΕΔ14
࣮ࡍʹͬͯΈΑ͏15
LithoͰॻ͍ͯΈͨdependencies {//Lithocompile 'com.facebook.litho:litho-core:0.2.0'//contains basic componentcompile 'com.facebook.litho:litho-widget:0.2.0'//annotation processorcompile 'com.facebook.litho:litho-annotations:0.2.0'annotationProcessor 'com.facebook.litho:litho-processor:0.2.0'//for Yoga. Yoga has dependencies for nativecompile 'com.facebook.soloader:soloader:0.2.0'//Stetho plugindebugCompile 'com.facebook.litho:litho-stetho:0.2.0'}16
LithoͰॻ͍ͯΈͨpublic class SplashActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);//ContextͷαϒΫϥεͰϥΠϒϥϦͰ͏ใΛཧ͢Δfinal ComponentContext c = new ComponentContext(this);setContentView(//ComponentΛmountͨ͠ViewGroupΛฦ͢LithoView.create(this,SplashComponent.create(c).build()));}}17
ComponentΛॻ͍ͯΈͨ//ଞͷίϯϙʔωϯτΛ·ͱΊΔίϯϙʔωϯτ@LayoutSpecpublic class SplashComponentSpec {@OnCreateLayoutstatic ComponentLayout onCreateLayout(ComponentContext c) {return Column.create(c).child(Text.create(c).text(“Hello Litho :)”).textSizeSp(30f).withLayout().alignSelf(YogaAlign.CENTER)).justifyContent(YogaJustify.CENTER).build();}}18
ComponentΛॻ͍ͯΈͨ19
ίʔυͷઆ໌20
LithoͰॻ͍ͯΈͨpublic class SplashActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);//ContextͷαϒΫϥεͰϥΠϒϥϦͰ͏ใΛཧ͢Δfinal ComponentContext c = new ComponentContext(this);setContentView(//ComponentΛmountͨ͠ViewGroupΛฦ͢LithoView.create(this,SplashComponent.create(c).build()));}}21
LithoͰॻ͍ͯΈͨpublic class SplashActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);//ContextͷαϒΫϥεͰϥΠϒϥϦͰ͏ใΛཧ͢Δfinal ComponentContext c = new ComponentContext(this);setContentView(//ComponentΛmountͨ͠ViewGroupΛฦ͢LithoView.create(this,SplashComponent.create(c).build()));}}22
LithoͰॻ͍ͯΈͨpublic class SplashActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);//ContextͷαϒΫϥεͰϥΠϒϥϦͰ͏ใΛཧ͢Δfinal ComponentContext c = new ComponentContext(this);setContentView(//ComponentΛmountͨ͠ViewGroupΛฦ͢LithoView.create(this,SplashComponent.create(c).build()));}}23
ComponentΛॻ͍ͯΈͨ//ଞͷίϯϙʔωϯτΛ·ͱΊΔίϯϙʔωϯτ@LayoutSpecpublic class SplashComponentSpec {@OnCreateLayoutstatic ComponentLayout onCreateLayout(ComponentContext c) {return Column.create(c).child(Text.create(c).text(“Hello Litho :)”).textSizeSp(30f).withLayout().alignSelf(YogaAlign.CENTER)).justifyContent(YogaJustify.CENTER).build();}}24
ComponentΛॻ͍ͯΈͨ//ଞͷίϯϙʔωϯτΛ·ͱΊΔίϯϙʔωϯτ@LayoutSpecpublic class SplashComponentSpec {@OnCreateLayoutstatic ComponentLayout onCreateLayout(ComponentContext c) {return Column.create(c).child(Text.create(c).text(“Hello Litho :)”).textSizeSp(30f).withLayout().alignSelf(YogaAlign.CENTER)).justifyContent(YogaJustify.CENTER).build();}}25
ComponentͷΞϊςʔγϣϯ• LayoutSpecʢྫɿListItemʣ• ଞͷίϯϙʔωϯτΛಛఆͷϨΠΞτʹ·ͱΊΔɻViewGroupతͳଘࡏ• MountSpecʢྫɿRecyclerʣ• ViewDrawableΛϨϯμϦϯά͢ΔɻϨΠΞτͷܭࢉॲཧ͕ఆٛ͞ΕΔ26
ComponentΛॻ͍ͯΈͨ//ଞͷίϯϙʔωϯτΛ·ͱΊΔίϯϙʔωϯτ@LayoutSpecpublic class SplashComponentSpec {@OnCreateLayoutstatic ComponentLayout onCreateLayout(ComponentContext c) {return Column.create(c).child(Text.create(c).text(“Hello Litho :)”).textSizeSp(30f).withLayout().alignSelf(YogaAlign.CENTER)).justifyContent(YogaJustify.CENTER).build();}}27
ComponentΛॻ͍ͯΈͨ//ଞͷίϯϙʔωϯτΛ·ͱΊΔίϯϙʔωϯτ@LayoutSpecpublic class SplashComponentSpec {@OnCreateLayoutstatic ComponentLayout onCreateLayout(ComponentContext c) {return Column.create(c).child(Text.create(c).text(“Hello Litho :)”).textSizeSp(30f).withLayout().alignSelf(YogaAlign.CENTER)).justifyContent(YogaJustify.CENTER).build();}}28
Layout• ֤ཁૉΛͲͷํʹஔ͢Δ͔ܾΊΔίϯςφ• ೖΕࢠʹͯ͠ෳࡶͳஔΛߦ͏͜ͱՄೳͰ͋Δ//linear layout: horizontalRow.create(c).child(…).build();//linear layout: verticalColumn.create(c).child(…).build();29
ComponentΛॻ͍ͯΈͨ//ଞͷίϯϙʔωϯτΛ·ͱΊΔίϯϙʔωϯτ@LayoutSpecpublic class SplashComponentSpec {@OnCreateLayoutstatic ComponentLayout onCreateLayout(ComponentContext c) {return Column.create(c).child(Text.create(c).text(“Hello Litho :)”).textSizeSp(30f).withLayout().alignSelf(YogaAlign.CENTER)).justifyContent(YogaJustify.CENTER).build();}}30
ComponentΛॻ͍ͯΈͨ//ଞͷίϯϙʔωϯτΛ·ͱΊΔίϯϙʔωϯτ@LayoutSpecpublic class SplashComponentSpec {@OnCreateLayoutstatic ComponentLayout onCreateLayout(ComponentContext c) {return Column.create(c).child(Text.create(c).text(“Hello Litho :)”).textSizeSp(30f).withLayout().alignSelf(YogaAlign.CENTER)).justifyContent(YogaJustify.CENTER).build();}}31
Component• ࠷ॳ͔Β༻ҙ͞Ε͍ͯΔجຊతͳView//like TextViewText.create(c);//like ImageViewImage.create(c);32
Recycler@OnCreateLayoutstatic ComponentLayout onCreateLayout(final ComponentContext c) {final RecyclerBinder recyclerBinder = new RecyclerBinder(c,new LinearLayoutInfo(c, VERTICAL, false));final RecyclerEventsController controller = new RecyclerEventsController();//...return Recycler.create(c).binder(recyclerBinder).recyclerEventsController(controller).refreshHandler(ListComponent.onPTRrefresh(c, recyclerBinder, controller)).onScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);//...}}).build();}33
Recycler@OnCreateLayoutstatic ComponentLayout onCreateLayout(final ComponentContext c) {final RecyclerBinder recyclerBinder = new RecyclerBinder(c,new LinearLayoutInfo(c, VERTICAL, false));final RecyclerEventsController controller = new RecyclerEventsController();//...return Recycler.create(c).binder(recyclerBinder).recyclerEventsController(controller).refreshHandler(ListComponent.onPTRrefresh(c, recyclerBinder, controller)).onScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);//...}}).build();}34
Recycler• dataͱviewΛbind͢ΔଘࡏɻAdpaterͱLayoutManagerͷػೳΛ࣋ͭ• ୈೋҾʹLayoutManagerΛwrap͢ΔLayoutInfoΛͱΔɻ• LinearLayoutInfoͷ߹ɺLinearLayoutManagerΛ෦తʹ͍࣋ͬͯΔ• GridLayoutInfo༻ҙ͞Ε͍ͯΔfinal RecyclerBinder recyclerBinder = new RecyclerBinder(c,new LinearLayoutInfo(c, VERTICAL, false));35
Recycler@OnCreateLayoutstatic ComponentLayout onCreateLayout(final ComponentContext c) {final RecyclerBinder recyclerBinder = new RecyclerBinder(c,new LinearLayoutInfo(c, VERTICAL, false));final RecyclerEventsController controller = new RecyclerEventsController();//...return Recycler.create(c).binder(recyclerBinder).recyclerEventsController(controller).refreshHandler(ListComponent.onPTRrefresh(c, recyclerBinder, controller)).onScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);//...}}).build();}36
Recycler@OnCreateLayoutstatic ComponentLayout onCreateLayout(final ComponentContext c) {final RecyclerBinder recyclerBinder = new RecyclerBinder(c,new LinearLayoutInfo(c, VERTICAL, false));final RecyclerEventsController controller = new RecyclerEventsController();//...return Recycler.create(c).binder(recyclerBinder).recyclerEventsController(controller).refreshHandler(ListComponent.onPTRrefresh(c, recyclerBinder, controller)).onScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);//...}}).build();}37
Recycler• RecyclerRecyclerViewΛ෦తʹ࣋ͭRecyclerViewWrapperΛ༗͢Δ• RecyclerViewWrapperSwipeRefreshLayoutΛܧঝ͍ͯ͠Δ• ͜ΕΛίϯτϩʔϧ͢Δ͢ΔͨΊʹcontrollerΛ࡞͍ͯ͠Δfinal RecyclerEventsController controller = new RecyclerEventsController();38
Recycler@OnCreateLayoutstatic ComponentLayout onCreateLayout(final ComponentContext c) {final RecyclerBinder recyclerBinder = new RecyclerBinder(c,new LinearLayoutInfo(c, VERTICAL, false));final RecyclerEventsController controller = new RecyclerEventsController();//...return Recycler.create(c).binder(recyclerBinder).recyclerEventsController(controller).refreshHandler(ListComponent.onPTRrefresh(c, recyclerBinder, controller)).onScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);//...}}).build();}39
Recycler@OnCreateLayoutstatic ComponentLayout onCreateLayout(final ComponentContext c) {final RecyclerBinder recyclerBinder = new RecyclerBinder(c,new LinearLayoutInfo(c, VERTICAL, false));final RecyclerEventsController controller = new RecyclerEventsController();//...return Recycler.create(c).binder(recyclerBinder).recyclerEventsController(controller).refreshHandler(ListComponent.onPTRrefresh(c, recyclerBinder, controller)).onScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);//...}}).build();}40
Event• EventΛड͚औΓ͍ͨComponentʹఆٛ͢Δ@OnEvent(PTRRefreshEvent.class)static void onPTRrefresh(final ComponentContext c,@Param final RecyclerBinder recyclerBinder,@Param final RecyclerEventsController controller){//…controller.clearRefreshing();}41
Stetho plugin• Stetho(v1.5.0)ͱ࿈ܞ• re-start / re-compileͤͣʹमਖ਼Ͱ͖Δ• ࣮ࡍʹඍमਖ਼ΛՃ͑ͳ͕Βಈ͖Λ͔֬ΊΒΕΔ• Yogaͱ༑ୡʹͳΕͦ͏• AndroidStudioͷlayout inspectorͰϑϥοτͳViewʹͳ͍ͬͯΔ42
StethoͰ͍ͬͯ͡ΈΑ͏43
debug options• ComponentsConfigurationͷΛtrueʹ͢ΔͱλονΤϦΞͱViewͷڥքΛͦΕͧΕՄࢹԽͰ͖Δ44
debug optionsᶃdebugHighlightInteractiveBoundsλονΤϦΞΛՄࢹԽ45
debug optionsᶄdebugHighlightMountBoundsViewͷڥքΛՄࢹԽ46
ͬͯΈͯͲ͏͔ͩͬͨ• xmlͰͳ͍ܗͰViewΛΉͷ͕৽ͩͬͨ• YogaʹΑΓࣗಈతʹྑ͍Ґஔʹௐ͞ΕΔͷָ͕47
ͬͯΈͯͲ͏͔ͩͬͨ• ίʔυΛ͍ʹ͍͘• ࣗಈੜޙͷΫϥεΛࢀর͢ΔͨΊɺfind usage͕Γʹ͍͘• annotation processorނʹίϯύΠϧϑΣʔζʹ࣮ߦ͞ΕΔ• SpecʹΞϊςʔγϣϯΛՃͨ͠߹࠶Ϗϧυ͕ඞཁͰ͋Δ• δΣενϟʔΞχϝʔγϣϯΛ͏μΠφϛοΫͳUIࠓͷͱ͜Ζͳ͍• ࠓޙՃ͞ΕΔΒ͍͠• xmlͰͷैདྷͷϨΠΞτΛͯ͢ସ͢ΔͷͰͳ͍• AndroidStudioͰPreview͢Δ͜ͱ͕Ͱ͖ͳ͍48
ͬͯΈͯͲ͏͔ͩͬͨ• ෳࡶͳViewHolderͷΈ߹Θ͕ͤͳ͍ݶΓෆཁʁ49
ͬͱௐ͍ࠪͨ͠ͱ͜Ζ• Asynchronous Layout• Immutability and thread safety• http://fblitho.com/docs/asynchronous-layout• UIεϨουʹ͢લʹlayoutͱmeasureΛߦ͍ͬͯΔ• ͲͷΑ͏ʹόοΫάϥϯυͰॲཧ͞ΕͯΔͷ͔ʁ50
͓ΘΓ51
Notes
༨ஊɿඌͷSpecͱ• ྫɿNameComponentSpec• ίϯϙʔωϯτͷ໊લSpecͰऴΘΒͤΔ• annotation processorͰNameComponentʹͳΔ• javapoet༻͍ͯ͠ΔɻඌʹSpecΛ͚ͭΔɻ• ɾhttps://github.com/facebook/litho/blob/master/litho-processor/src/main/java/com/facebook/litho/specmodels/model/SpecModelValidation.java#L5353
࡞ऀʹΑΔհϒϩά• Lithoʹ͍͔ͭͯΓ͘͢հ͞Ε͍ͯΔ• https://code.facebook.com/posts/1187475984695956/open-sourcing-litho-a-declarative-ui-framework-for-android/• LithoͷίϯηϓτɿComponents for Android• https://code.facebook.com/posts/531104390396423/components-for-android-a-declarative-framework-for-efficient-uis54
ϒϩάͳͲ• CustomButtonΛͭͬͯ͘Έͨ• https://medium.com/@p.tournaris/litho-how-to-create-a-custom-button-b460b5a3b828• LithoͷReview• https://medium.com/proandroiddev/facebook-litho-review-b591372d85be• DroidCon London 2016• https://skillsmatter.com/skillscasts/8418-flat-as-a-pancake#video55
ͦͷଞ• https://news.ycombinator.com/item?id=14142321• https://www.reddit.com/r/androiddev/comments/68i0hg/review_of_the_new_facebook_litho_framework_part_1/• https://www.reddit.com/r/androiddev/comments/66g0ve/facebook_for_developers_litho_a_declarative/• https://www.reddit.com/r/androiddev/comments/6659bn/litho_a_declarative_ui_framework_for_android/• https://www.reddit.com/r/androiddev/comments/6784bv/glide_imageloading_component_for_litho/56