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
Navigationの採用を検討してみて、 色々考えた話を共有するよ
Search
kobashin
July 25, 2019
Programming
310
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Navigationの採用を検討してみて、 色々考えた話を共有するよ
kobashin
July 25, 2019
More Decks by kobashin
See All by kobashin
Mix Leap Study #69 Yahoo!ショッピング+PayPayフリマのSRE事例 ショッピングのSREチームとして試したこと、 失敗したこと
shinjikobayashi
0
310
気になったセッションから今すぐ開発に導入したいあれこれ(主観
shinjikobayashi
2
760
Systemアプリ開発入門
shinjikobayashi
6
13k
Other Decks in Programming
See All in Programming
dRuby over BLE
makicamel
2
320
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
190
密結合なバックエンドから TypeScript のコードを生成する
kemuridama
1
740
Modding RubyKaigi for Myself
yui_knk
0
890
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
110
プラグインで拡張される Context をtype-safe にする難しさと設計判断
kazupon
2
590
ユニットテストの先へ:テスト技法で要求・仕様を整理するJava開発実践 / Beyond_Unit_Testing_Practical_Java_Development_Techniques_for_Organizing_Requirements_and_Specifications
shimashima35
0
350
AIエージェントの隔離技術の徹底比較
kawayu
0
460
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
140
柔軟なPDFレイアウトエディタを支える型システム設計 — Discriminated UnionとConditional Typeの実践
minako__ph
4
1.4k
Datadog × OpenTelemetry 入門と実践のあいだ
kn_to_maxpno
1
150
セキュリティの専門家じゃなくてもできる。「セキュリティ意識」をアップデートして サプライチェーン攻撃への耐性を高めよう。
tk3fftk
5
640
Featured
See All Featured
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.1k
It's Worth the Effort
3n
188
29k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.5k
Facilitating Awesome Meetings
lara
57
6.9k
A Tale of Four Properties
chriscoyier
163
24k
BBQ
matthewcrist
89
10k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
200
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
140
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
360
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.3k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
350
Transcript
Mix Leap Study #47 Android x Flutterษڧձ Navigationͷ࠾༻Λݕ౼ͯ͠Έͯɺ ৭ʑߟ͑ͨΛڞ༗͢ΔΑ @kobashinG
ʢ͜͠Μʣ 2019.7.25
ࣗݾհ ͜͠Μ (@kobashinG) Ϡϑʔגࣜձࣾ ϑϩϯτΤϯυશൠ, etc… Android, Linux, k8s, ˏϠϑʔγϣοϐϯά
ΏΔΏΔͱ͓͠·͢ ࠓͷAgenda • Android Jetpack Navigationͱʁ • SingleActivity or Multi-Activityʁ
• BottomNavigationͱҰॹʹ͍͍ͨ • ͦͷଞ (※࣌ؒΓͳ͍߹ඈ͔͢)
Android Jetpack Navigationͱʁ (ຊɺNavigation 2.1.0-beta02࣌ͷΛ͠·͢)
Android Jetpack Navigationͱʁ
Android Jetpack Navigationͱʁ • Navigation GraphΛͬͨUIભҠͷ࣮ • UIͷભҠΛActivity/Fragment͔ΒҠৡՄೳ • ࡶͳFragmentTransactionΛॻ͔ͳͯ͘ࡁΉ
• Fragmentؒͷσʔλड͚͠Λܕ҆શʹʢSafeArgsʣ • DeepLinkΛ؆қ࣮Մೳʹ
3͍ํߨ࠲ Navigation Editor (·ͨxml) Ͱը໘ભҠΛ࡞͢Δ
3͍ํߨ࠲ ActivityʹNavHostFragmentΛஔ͢Δ <androidx.constraintlayout.widget.ConstraintLayout > <fragment android:layout_width="0dp" android:layout_height="0dp" android:id="@+id/root_nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" app:navGraph="@navigation/navigation_graph_with_bottom_nav"
app:defaultNavHost="true" app:layout_goneMarginBottom="0dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/bottom_navigation" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"/> LayoutʹNavHostFragment Λஔ͢Δ ઌఔ࡞ͨ͠ɺnavigation.xml Λࢦఆ͢Δ layout/activity_main.xml
3͍ํߨ࠲ ͜Ε͚ͩ
࠷ۙͷ New Features(ݸਓతʹྑ͍ͱࢥͬͨͭ) 2.1.0-alpha02 • NaviGraphͷείʔϓʹ߹ΘͤͨViewModel͕ੜՄೳʹ private val viewModel: SearchViewModel
by navGraphViewModels<SearchViewModel>(R.id.nav_search) ͜ͷείʔϓͰར༻͍ͨ͠ ViewModeΛએݴͰ͖Δ src/SearchFragment.kt
࠷ۙͷ New Features(ݸਓతʹྑ͍ͱࢥͬͨͭ) 2.1.0-alpha03ɹ<dialog />͕ར༻Մೳʹ <navigation> <fragment android:id="@+id/nav_fav" android:name="kobashin.com.navigation_sample.FavoriteFragment" android:label="FavoriteFragment"
tools:layout="@layout/fragment_favorite" > <action android:id="@+id/action_nav_fav_to_nav_modal" app:destination="@id/nav_modal" /> </fragment> <dialog android:id="@+id/nav_modal" android:name="kobashin.com.navigation_sample.BottomSheetFragment" android:label="BottomSheet" tools:layout="@layout/fragment_bottom_sheet"/> </navigation> BottomSheetDialogFragment DialogFragmentͳͷͰ ར༻Ͱ͖Δ navigation/navigation_graph.xml
Single-Activity or Multi-Activities? NavigationొޙɺͲ͏ͯ͠·͢ʁ
୲͍ͯ͠ΔΞϓϦʹΈࠐΉͱͨ͠Βʁ ͍· • Multi Activity • ࣌ʹFragment • Navigationܥ Libraryແ͍
• Single ActivityԽʁ • Navigationʁ Ͳ͏͢Δʁ
Ϣʔβʔͷಋઢ͔Βݕ౼͢Δ Top SearchTop Search ItemDetail
Ϣʔβʔͷಋઢ͔Βݕ౼͢Δ Top SearchTop Search ItemDetail Top TOP SearchTop TOP SearchTop
Search TOP SearchTop Search ItemDetail ͜͜ͷભҠΛؾ࣋ͪΑ͘ ͍ͨ͠ʂ
ActivityͱFragmentͷભҠͬͯ͘܁Γฦ͢ͱʁ • ActivityભҠɺFragmentભҠΛ܁Γฦ͢ • ڥ • Pixcel3 • OS ver
9 • UIదɻϘλϯ͚̍ͭͩɻ ݕূͯ͠ΈΔɻ
దͳΞϓϦͰը໘ભҠΛ܁Γฦͯ͠ΈΔ Activityฤ @Test fun mainActivityTest() { for (x in 0..1000)
{ val appCompatButton = onView( allOf( withId(R.id.button), withText("button"), childAtPosition( childAtPosition( withId(android.R.id.content), 0 ), 1 ), isDisplayed() ) ) appCompatButton.perform(click()) } } class SearchActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_search) button.setOnClickListener { startActivity(Intent(this, ItemDetailActivity::class.java)) } } } src/SearchActivity.kt androidTest/MainActivityTest.kt
దͳΞϓϦͰը໘ભҠΛ܁Γฦͯ͠ΈΔ Activityฤ ։࢝ޙɺ͍͍ͩͨ 300msఔͰը໘ભҠͯ͠ ͍Δ ऴྃલɺ͍͍ͩͨ1200ms ఔͰը໘ભҠ͍ͯ͠Δ ϝϞϦ༻ྔ͕1Gఔ·Ͱ ૿ՃˠGCΛ܁Γฦ͢ ։࢝ޙ
ऴྃલ
దͳΞϓϦͰը໘ભҠΛ܁Γฦͯ͠ΈΔ Fragmentฤ class SearchFragment : Fragment() { override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_search, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) button.setOnClickListener { findNavController() .navigate(R.id.action_searchFragment_to_itemDetailFragment) } } } ※ςετίʔυ΄΅ಉ͡ src/SearchFragment.kt
దͳΞϓϦͰը໘ભҠΛ܁Γฦͯ͠ΈΔ ը໘ભҠͷಈ͖͕͏·͘औΕ͍ͯͳ͍͕ɺςετ࣮ ߦ࣌ؒ5minऑʢActivity൛15minڧʣ ϝϞϦ༻ྔGCΛ܁Γฦ͠ͳ͕Βඍ૿ɻ ࠷ऴతʹ100Mఔ·Ͱ૿Ճ Fragmentฤ
ભҠ͚ͩʹண͢ΕFragmentͷํ͕Αͦ͞͏ʁ • ҙɿը૾ஔ͍ͯ͠ͳ͍దͳςετέʔεͰ͋Δ • ActivityΛੜ͢Δίετ܁Γฦ͢ʹ૿͍ͯ͘͠ • FragmentΛ༻͍ͨભҠͷύλʔϯίετখͦ͞͏ͩ • FragmentBackStackʹੵΈଓ͚ΔҝɺϦιʔεΛ อ࣋͢ΔύλʔϯͩͱϝϞϦޮѱԽ͍ͯ͘͠ʁ
• FragmentΛͬͨύλʔϯϝϞϦཧ͕͍͠ɻɻ
݁ہΔͳΒͲ͏͢Δஅͳͷʁ • Ϣʔβʔͷಈ͘ଠ͍ಋઢ෦FragmentͰભҠ ͦͷଞActivityͰભҠ͢ΔΞʔΩςΫνϟʹͨ͠ • ࣌ؒ܁Γฦ͠ར༻ͯ͠ཉ͍͠ը໘ؒͰͷભҠΛ FragmentͰͷભҠͱ͢Δ͜ͱͰUXΛྑ͔ͨͬͨ͘͠ ݕ౼՝ɿ ɹϦϦʔε࣌ʹεϖοΫͰͷଞOS VerͰͷݕূ
BottomNavigationͱҰॹʹ͍͍ͨ ಋೖ͚ͩͳΒɺ؆୯ɻͻͶΔͱͲ͏ͳΔʁ
BottomNavigationͱҰॹʹ͏ʹʁ <menu xmlns:android="http://schemas.android.com/apk/res/ android"> <item android:id="@+id/nav_top" android:icon="@drawable/ic_baseline_home_24px" android:title="top" /> <item
android:id="@+id/nav_search" android:icon="@drawable/ic_baseline_search_24px" android:title="search" /> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/navigation_graph" app:startDestination="@id/nav_top"> <fragment android:id="@+id/nav_top" android:name="kobashin.com.navigation_sample.TopFragment" android:label="TopFragment" tools:layout="@layout/fragment_top" > <action android:id="@+id/action_topFragment_to_searchFragment" app:destination="@id/nav_search" /> <action android:id="@+id/action_topFragment_to_mypageFragment" app:destination="@id/mypageFragment" /> </fragment> ͜͜Λἧ͑Δ menu/item_id navigation/fragment_id Λἧ͑Δ menu/menu.xml navigation/navigation_graph.xml
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {
// লུ val navController = findNavController(R.id.root_nav_host_fragment) setupActionBarWithNavController( navController, AppBarConfiguration( setOf( R.id.nav_top, R.id.nav_search, R.id.nav_fav, R.id.nav_my ) ) ) bottom_navigation.setupWithNavController(navController) navigation/fragment_id Λ͢ BottomNavigationͷ ॳظԽΛΕͳ͍ ActionBarͷॳظԽ࣌ʹ ભҠઌͷidsΛ͢ src/MainActivity.kt BottomNavigationͱҰॹʹ͏ʹʁ
BottomNavigationͷλϒຖʹBackStackΛอ͍࣋ͨ͠ • Top -> SearchͰλϒ͕ҠΔ • λϒҠಈͰλϒͷભҠ͕ඈͿ ՝ Γ͍ͨ͜ͱ •
֤λϒຖʹStackΛอ࣋ • λϒͷߦ͖དྷͰ෮ݩ͍ͨ͠
BottomNavigationͷλϒຖʹBackStackΛอ͍࣋ͨ͠
BottomNavigationͷλϒຖʹBackStackΛอ͍࣋ͨ͠ Activity FragmentManager Top༻ͷ NavHostFragment Search༻ͷ NavHostFragment Fav༻ͷ NavHostFragment MyPage༻ͷ
NavHostFragment navi_graph NavigationͰ NavHostFragmentͷ ChildFragmentManager Λ͏ͷͰ͜ΕΛ͚Δ
BottomNavigationͷλϒຖʹBackStackΛอ͍࣋ͨ͠ • BottomNavigationView. setOnNavigationItemSelectedListener ͰભҠઌͷNavHostFragmentΛࢦఆ͢Δ • Back੍ޚͰ֤λϒͷStack͕ແ͘ͳͬͨ ઌΛTopʹ͢ΔͨΊɺaddToBackStack() ͓ͯ͘͠ setOnNavigationItemSelectedListener
{ item -> val newlySelectedItemTag = graphIdToTagMap[item.itemId] fragmentManager.beginTransaction() .attach(selectedFragment) .setPrimaryNavigationFragment(selectedFragment) .apply { // Detach all other Fragments graphIdToTagMap.forEach { _, fragmentTagIter -> if (fragmentTagIter != newlySelectedItemTag) { detach( fragmentManager.findFragmentByTag(firstFragmentTag)!! ) } } } .addToBackStack(firstFragmentTag) .commit() } src/NavigationExt.kt λϒຖʹอ࣋͢Δ NavHostFragmentͷΓ ସ͑෦
BottomNavigationͷλϒຖʹBackStackΛอ͍࣋ͨ͠ https://github.com/googlesamples/android-architecture-components/blob/master/NavigationAdvancedSample/app/src/main/ java/com/example/android/navigationadvancedsample/NavigationExtensions.kt • googlesamples/android-architecture-components ʹࢀߟʹͳΔαϯϓϧ͕ެ։͞Ε͍ͯΔ • BackΩʔؔ࿈ͰखΛೖΕ͔ͨͬͨͷͰɺಈతʹੜ͢Δ NavHostFragmentΛ֦ு͠ɺBackΩʔ੍ޚΛݕ౼த
ͦͷଞ Multi-ModuleͰ͍͍ͨ
Fragmentͷ໊લղܾͷλΠϛϯάΛ୳Δ Activityͷىಈ LayoutͷಡΈࠐΈ NavHostFragment ͷॳظԽ NavHostController ͷॳظԽ Graphͷੜ app:startDestination Λىಈ
https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/navigation/navigation-fragment/src/main/java/androidx/ navigation/fragment/NavHostFragment.java#204
findNavController(). navigate() Destination ͷղܾ className͔Β FragmentΛੜ ભҠͱBackStack ͷίϯτϩʔϧ https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/navigation/navigation-fragment/src/main/java/androidx/ navigation/fragment/FragmentNavigator.java#151
໊લղܾͷλΠϛϯάΛ୳Δ
໊લղܾͷλΠϛϯάΛ୳Δ • Build࣌Ͱͳ͘ɺ࣮ߦ࣌ʹ໊લղܾΛߦ͍ͬͯΔ ͭ·ΓɺBuild࣌Ͱ͘ͳ͍ͬͯͯେৎʂ ֆ͕ग़ͳ͍ͷ͕ͪΐͬͱऐ͍͚͠ΕͲ <?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_search" app:startDestination="@id/searchFragment"> <fragment android:id="@+id/searchFragment" android:name=“jp.co.yahoo.android.xxx.xxx.SearchTopFragment" android:label="SearchFragment"> <action android:id="@+id/action_contents_search_to_search_result" app:destination="@id/searchResultFragment"/> </fragment> ࣮ߦ࣌ʹղܾͰ͖Δ FragmentΛࢦఆ͢Δ navigation/navigation_graph.xml
ෳͷϞδϡʔϧ͔ΒݺΕΔύλʔϯʹରԠ͢Δ
ෳͷϞδϡʔϧ͔ΒݺΕΔύλʔϯʹରԠ͢Δ • Կߟ͑ͣʹͬͯ͠·͏ͱʁ →ݩͷgraph͕͔Ε͍ͯΔͱɺFragmentͰݺͼݩΛ ɹߟྀͨ͠Action IdΛࢦఆ͢Δඞཁ͕Ͱ͖ͯͯ͠·͏ɻ Top Search ItemDetail ItemDetail
R.id.xxx R.id.yyy navigation_top.xml navigation_search.xml
ෳͷϞδϡʔϧ͔ΒݺΕΔύλʔϯʹରԠ͢Δ • Nested GraphΛ༻͍ΕGraphͷڞ௨Խ͕Ͱ͖Δ ສࣄղܾʂ
ͦͷଞ DialogͰNavigation͕͍ͨ͠ ͋·Γ6*తʹྑ͘ͳͦ͞͏
DialogͰNavigation͕͍ͨ͠ • ͋·ΓDialogͰը໘ભҠ͢Δͷྑ͘ͳ͍(ͱࢥ͏) • ͰͲ͏͍ͯͨ͠͠Μͩʂ ઌͷྫʹ͋ΔΑ͏ʹɺ ಈతʹNavHostFragmentΛ͍͚ͭͬͯ͘Δʂ
DialogͰNavigation͕͍ͨ͠ class SearchModalFragment : BottomSheetDialogFragment() { override fun onViewCreated(view: View,
savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val fragment = NavHostFragment.create(R.navigation.modal_navi_graph) childFragmentManager.beginTransaction() .replace(R.id.container_nav_host, fragment) .commit() } • Activityʹஔ͘Α͏ʹɺ <fragment /> Λॻ͘ͱ࠶ੜ࣌ʹΤϥʔʹͳΔ • ಈతʹੜ͢Εे͑Δ ίϯετϥΫλ͕ੜ͑ͯΔͷ ͰͦΕΛ͏ src/SearchModalFragment.kt
ॴײ
ॴײ • طʹ୯७ͳͱ͜ΖͰϋϚΔ͜ͱ΄΅ແ͍ • ͪΐͬͱҳͨ͜͠ͱΛΓͨ͘ͳΔͱɺ ίʔυΛಡ·ͳ͍ͱΠέͳ͍ͷ͍ͭͷAndroid͞Μ • όʔδϣϯΞοϓͰΰϦΰϦมΘ͍ͬͯ͘ͷͰɺ ࠾༻͢Δόʔδϣϯཁݕ౼ɻྗ͕ඞཁͩ
͜Μͳ͜ͱͰ͖ΔΜ͡Όͳ͍͔ͳʁ • Ұ෦ػೳʹݶఆͨ͠֎෦Intentެ։ • ݺͼग़͠༻ͷActivityΛ༻ҙɺಡΈࠐΉgraphΛม͑Εʁ • ݱࡏઃఆ͍ͯ͠ΔgraphΛΩʔʹͨ͠Navigatorʁ • ෳͷgraph͔Βݺͼग़͞ΕΔFragmentͰॊೈੑ֬อ ·ͨௐ͓ͯ͠·͢Ͷʂ
☺
͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ