Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Navigation X

Navigation X

Android Navigation Architecture Components를 Production에 적용하면서 부딪혔던 문제점들과 해결한 내용들을 간단히 공유합니다.

Sungyong An

July 30, 2019
Tweet

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. ViewModel ViewModel NavGraph NavGraph DialogFragment Fragment Fragment Fragment Overview ViewModel

    ViewModel ViewModel ViewModel Fragment Scope
 ViewModelب оמ
  2. navigate() + SafeArgs • -Directions, -Args ௿ېझܳ ࢤࢿೞৈ
 ചݶ рী

    Type-safe чਸ ੹׳ೠ׮. • Parcelize, Serializable, Enum ೲਊ // Send controller.navigate( MasterFragmentDirections .actionToDetail(/*id*/ "ID1234")
 ) // Receive val safeArgs: DetailFragmentArgs by navArgs() safeArgs.id // = ID1234 id: ID1234
  3. navigate() + popUpTo • ౠ੿ ਤ஖ө૑ غجইр റ,
 ׮਺ ചݶਵ۽

    ੉زೠ׮. <action android:id="@+id/action_to_login" app:destination=“@id/login" app:popUpTo="@id/splash" app:popUpToInclusive="true" />
  4. navigate() + singleTop • э਷ ചݶਸ োࣘਵ۽ ऺ૑ ঋҊ,
 ചݶਸ

    Ү୓ೠ׮. <action android:id="@+id/action_to_detail" app:destination=“@id/detail" app:launchSingleTop="true" />
  5. Backward navigation • ੉੹ ചݶਵ۽ جইр׮. • (1) Up ߡౡ

    ز੘, Task ઙܐח ! • (2) Back ߡౡ ز੘ • (3) ౠ੿ ݾ੸૑ө૑ Back ز੘ val controller = findNavController()
 controller.navigateUp() // (1) 
 controller.popBackStack() // (2) 
 controller.popBackStack( // (3)
 /* destinationId */ R.id.home,
 /* inclusive */ false
 )
  6. With Shared Elements • Activity, Fragment рী
 SharedElements ӝמب ૑ਗೠ׮.

    val sharedElements: Array<Pair<View, String>> … // Detail੉ Activity ੌ ٸ, findNavController().navigate( actionToDetail(), ActivityNavigatorExtras( activityOptions = ActivityOptionsCompat .makeSceneTransitionAnimation( activity, *sharedElements)) ) // Detail੉ Fragment ੌ ٸ, findNavController().navigate( actionToDetail(), FragmentNavigatorExtras(*sharedElements) )
  7. N A V I G A T I O N

    ! Activity to Fragment
  8. • Fragmentח LifecycleOwner׮. • ׮਺ ചݶਵ۽ ੉ز द, ӝઓ Fragmentח

    onDestroyView()ө૑݅ ഐ୹ػ׮.
 (Activityীࢲח onStop݅ ഐ୹غח Ѫ୊ۢ, onDestroyח ഐ୹غ૑ ঋח׮.) • ചݶਸ খ/ٍ۽ ੉زೞݶ, ੉੹ LiveDataب ҅ࣘ observeೞѱ ػ׮. • ೧Ѿߑߨ: Fragmentীࢲח ViewLifecycleOwnerܳ ࢎਊೞ੗. LiveData۽ чਸ ੹׳ೡ ٸ, LifecycleOnwer ઱੄ 1. Fragment +(AAC) ViewModel Link: https://medium.com/androiddevelopers/690a384218f2
  9. | Fragment +(AAC) ViewModel class HomeUiModel(val items: List<BookItem>) // ViewModel.kt

    private val _uiModel = MediatorLiveData<HomeUiModel>() val uiModel: LiveData<HomeUiModel> get() = _uiModel // Fragment.kt homeViewModel.uiModel.observe(this) { listAdapter.submitList(it.items) } viewLifecycleOwner) { LifecycleOwner ઱੄
  10. • Fragmentח View݅ Destroyؼ ࣻ ੓׮. • Destroyed RecyclerViewо adapterܳ

    ଵઑೞҊ ੓ਵݶ Leak੉ ߊࢤೠ׮. • ؀উ 1: onCreateView(), onViewCreated()ীࢲ݅ adpater ଵઑೠ׮. • ؀উ 2: onDestroyView()ীࢲ adapterܳ null۽ ଵઑܳ ೧ઁೠ׮. Fragmentীࢲ Adapterܳ ଵઑೡ ٸ, Leak ߊࢤ оמ 2. RecyclerView Leak Link: https://medium.com/@yfujiki/927460532d53
  11. | RecyclerView Leak class MyFragment : Fragment() { private val

    adapter = MyAdapter() override fun onViewCreated(...) { super.onViewCreated(view, savedInstanceState) recyclerView.adapter = adapter } } NOT OK Fragmentо
 Adapter ଵઑ
  12. | RecyclerView Leak class MyFragment : Fragment() { override fun

    onViewCreated(...) { super.onViewCreated(view, savedInstanceState) recyclerView.adapter = MyAdapter() } } #1 Viewীࢲ݅ Adapter ଵઑ
  13. | RecyclerView Leak class MyFragment : Fragment() { private val

    adapter = MyAdapter() override fun onViewCreated(...) { super.onViewCreated(view, savedInstanceState) recyclerView.adapter = adapter } override fun onDestroyView() { super.onDestroyView() recyclerView.adapter = null } } #2 Destroy View
 ীࢲ੄ ଵઑ ઁѢ
  14. • ചݶ ߹۽ ৔৉ਸ ׮ܰѱ ࢸ੿೧ঠ ೞח ҃਋, (ex. Edge-to-edge)


    ۠ఋ੐ী SystemUiVisibilityܳ ߸҃೧ঠ ೠ׮. • NavDestination ੹ജ ઺ Immersive ݽ٘о ߸҃غݶ, ۨ੉ইਓ੉ ޻۰ࠁੌ ࣻ ੓׮. • ؀উ: !. ੜ ҳഅ೧ࠁ੗. ೞա੄ Windowܳ ৈ۞ Fragmentীࢲ ೣԋ ࢎਊ 3. SystemUiVisibility
  15. N A V I G A T I O N

    ! ইए਍ ӝמٜ
  16. • ؀উ #1: Global Actionਵ۽ ߄Դ׮. (੉ ҃਋, ചݶ਷ 2ߣ

    ࢤࢿػ׮.) • ؀উ #2: RxBinding ١ਸ ੉ਊೞৈ Debounce Clickਸ ҳഅೠ׮. Local Actionਸ ৈ۞ߣ ഐ୹ೞݶ, Exception ߊࢤ 1. Double Local Action // MasterFragment.kt findNavController.navigate(actionToDetail(id = "ID")) findNavController.navigate(actionToDetail(id = "ID"))
  17. <!-- res/navigation/nav_graph.xml --> <fragment android:id="@+id/master" android:name=“.ui.master.MasterFragment"> <!-- Local Action -->

    <action android:id="@+id/action_to_detail" app:destination="@id/detail" /> </fragment> android:name=".ui.master.MasterFragment" /> <!-- Global Action --> <action android:id="@+id/action_to_detail" app:destination="@id/detail" /> | Double Local Action #1 Global Actionਵ۽
 Intent୊ۢ ࢎਊ
  18. | Double Local Action class OnDebounceClickListener( private val listener: (View)

    -> Unit ) : View.OnClickListener { override fun onClick(view: View?) { val now = System.currentTimeMillis() if (now - lastTime < INTERVAL) return lastTime = now view?.run(listener) } } companion object { private const val INTERVAL: Long = 300 private var lastTime: Long = 0 } #2 Click Debounce ୊ܻ
 (or Rx, ௏ܖ౯ب оמ)
  19. • ӝઓী Activityীࢲ onBackPressed()ܳ ࢎਊೞח ҃਋, ୶о ҳഅ೧ঠ ೠ׮. •

    ؀উ #1: OnBackPressedListenerܳ ૒੽ ҳഅೠ׮. • ؀উ #2: AndroidX੄ OnBackPressedCallbackਸ ࢎਊೠ׮. Fragmentীח onBackPressed()о হ਺ 2. Handling Back Key
  20. | Handling Back Key #1 interface OnBackPressedListener { fun onBackPressed():

    Boolean } class MyFragment : Fragment(), OnBackPressedListener { ... } class MainActivity : AppCompatActivity() { override fun onBackPressed() { if (handleBackEventInChildFragment()) return super.onBackPressed() } } interface ୶о
  21. | Handling Back Key #1 class MainActivity : AppCompatActivity() {

    ... private fun handleBackEventInChildFragment(): Boolean { val current = navHostFragment .childFragmentManager .fragments.elementAtOrNull(0) if (current is OnBackPressedListener) { return current.onBackPressed() } return false } } Front Fragment੄
 onBackPressed() ഐ୹
  22. | Handling Back Key #2 Link: https://developer.android.com/guide/navigation/navigation-custom-back class MyFragment :

    Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val callback = requireActivity() .onBackPressedDispatcher .addCallback(this) { // Handle the back button event } } } ୭ࣗೠ Start ࢚క ੉റী
 Callback ഐ୹ ࠁ੢ ೞ૑݅ ୭नߡ੹ ೙ਃ! androidx.activity 1.0.0 androidx.fragment 1.1.0
  23. • ই݃ب Fragment Transactionীח Result ӝמ੉ হযࢲ? • ؀উ #1:

    ActivityResultо ೙ਃೠ ചݶ਷ ߹ب Activity۽ ܻ࠙ೠ׮. (ex. ۽Ӓੋ) • ؀উ #2: Shared (AAC) ViewModelਸ ࢎਊೠ׮. (feat. NavGraph) • ؀উ #3: (ࢿҕৈࠗ݅ ೙ਃೠ ҃਋) Result Codeܳ ߹ب੄ Channel۽ ҙܻೠ׮. ੉੹ ചݶਵ۽ Resultܳ ੹׳ೞח ӝמ হ਺ 3. No ActivityResult Link: https://issuetracker.google.com/issues/79672220
  24. | No ActivityResult // Fragment.kt startActivityForResult(Intent(...), RequestCode.EDIT_IMAGE) override fun onActivityResult(

    requestCode: Int,
 resultCode: Int,
 data: Intent?) { if (requestCode == RequestCode.EDIT_IMAGE && resultCode == Activity.RESULT_OK) { // ... do something ... } } #1 ӝઓҗ زੌೞѱ ࢎਊ
 (NavGraphী ನೣ X)
  25. | No ActivityResult Link: https://medium.com/androiddevelopers/df476b78144e #2 // Activity-Scoped ViewModel val

    sharedViewModel: ActivityViewModel by activityViewModels() // NavGraph-Scoped ViewModel val sharedViewModel: NavGraphViewModel by navGraphViewModels(R.id.nested_graph) <navigation app:startDestination=“..." ...> <navigation android:id=“@+id/nested_graph"> … </navigation> </navigation> Activity, NavGraph ױਤ
 Shared ViewModel ࢎਊ
  26. | No ActivityResult #3 class UpScene( @IdRes val destinationId: Int,

    val inclusive: Boolean = false, val requestCode: Int? = null ) : NavScene class LoginScene( private val nextScene: NavScene? = null ) : NavScene غجইт ചݶ
 (RequestCode > Result) (ex. ۽Ӓੋ ചݶ) ۽Ӓੋ റ, ׮਺ ചݶ ࢸ੿
  27. | No ActivityResult #3 fun onProfileTabClick() { val userId =

    currentUser?.id _navigateAction.event = if (userId != null) { ProfileScene(userId) } else { LoginScene(nextScene = UpScene( destinationId = R.id.home, inclusive = false, requestCode = RequestCode.SHOW_PROFILE )) } } ۽Ӓੋ ࢿҕ द, ക ചݶ > ೐۽೙ చ ಴द
  28. | No ActivityResult #3 // Login Success! _navigateAction.event = nextScene

    ?: DefaultScene is UpScene -> scene.run { if (findNavController().popBackStack(destinationId, inclusive)) { requestCode?.let { /* Save result to ResultManager */ } } } RequestCodeо ੓ਵݶ,
 Result ੷੢
  29. | No ActivityResult #3 // Fragment.kt override fun onResume() {

    super.onResume() dispatchFragmentResult() } private fun dispatchFragmentResult() { /* Restore result from ResultManager */ } onResume()ীࢲ അ੤ ചݶ੄ Result ഛੋ
  30. • ⚠ ViewModelਸ ࢎਊೡ ٸ, Fragment੄ LifecycleOwnerܳ ઱੄ೞࣁਃ. • ⚠

    RecyclerViewীࢲ Memory Leakਸ ഛੋ೧ࠁࣁਃ. • ⚠ ೞա੄ Windowܳ ҕਬೞৈ ઁযೞח ߑߨਸ Ҋ޹೧ঠ ೤פ׮. • Local Actionਸ ׮ܲ ചݶীࢲ ഐ୹ೞݶ Exception੉ ߊࢤؾפ׮. • ! Stable ߡ੹ীח Custom Back ӝמ੉ ઁҕغ૑ ঋणפ׮. • ! Result ӝמب ઁҕغ૑ ঋणפ׮. (ex. ActivityResult) • ! Multiple BackStack ӝמ੉ ೙ਃೞݶ, ҕध Sampleਸ ଵҊೞࣁਃ. Summary