일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Kotlin
- Singleton
- ㅓ
- 디자인패턴 #
- 함수형프로그래밍
- 디자인패턴
- 프로토타입 패턴
- Design Pattern
- 추상팩토리패턴
- 팩토리 메소드
- El
- Abstract Factory
- Functional Programming
- Observer Pattern
- designPattern
- F
- 빌터패턴
- builderPattern
- a
- 옵저버 패턴
- 추상 팩토리
- factory method
- PrototypePattern
- ㅋㅁ
- 싱글톤
- 코틀린
- r
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
Conditional navigation(조건부 네비게이션) 본문
앱에 대한 탐색을 디자인할 때 조건부 논리를 기반으로 한 destination과 다른 destination을 탐색할 수 있습니다.
예를 들어 사용자가 로그인해야 하는 destination에 대한 딥 링크를 따라갈 수 있거나 플레이어가 이기거나 지는 게임에서 다른 destination이 있을 수 있습니다.
User login
사용자가 인증이 필요한 프로필 화면으로 이동하려고 합니다. 인증이 필요하므로 사용자가 아직 인증되지 않은 경우 로그인 화면으로 리디렉션되어야 합니다.
로그인하려면 앱에서 사용자 이름과 비밀번호를 입력하여 인증할 수 있는 login_fragment로 이동해야 합니다
로그인이되면 사용자는 profile_fragment 화면으로 다시 전송됩니다.
로그인이되지 않으면 스낵바를 사용하여 자격 증명이 유효하지 않음을 사용자에게 알립니다.
사용자가 로그인하지 않고 프로필 화면으로 돌아가면 main_fragment 화면으로 보내집니다.
<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/nav_graph"
app:startDestination="@id/main_fragment">
<fragment
android:id="@+id/main_fragment"
android:name="com.google.android.conditionalnav.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/navigate_to_profile_fragment"
app:destination="@id/profile_fragment"/>
</fragment>
<fragment
android:id="@+id/login_fragment"
android:name="com.google.android.conditionalnav.LoginFragment"
android:label="login_fragment"
tools:layout="@layout/login_fragment"/>
<fragment
android:id="@+id/profile_fragment"
android:name="com.google.android.conditionalnav.ProfileFragment"
android:label="fragment_profile"
tools:layout="@layout/fragment_profile"/>
</navigation>
MainFragment에는 사용자가 프로필을 보기 위해 클릭할 수 있는 버튼이 포함되어 있습니다.
사용자가 프로필 화면을 보려면 먼저 인증해야 합니다
두 개의 fragment 을 사용하여 모델링되지만 사용자 상태에 따라 다릅니다.
1. login_fragment --> profile_fragment
2. profile_fragment
이 상태 정보는 두개의 fragment 중 하나의 책임이 아니며 sharedViewModel인 UserViewModel에 더 적절하게 보관됩니다.
UserViewModel은 ViewModelStoreOwner를 implement하는 Activity를 범위를 지정하여 fragment 간에 공유됩니다
MainActivity가 ProfileFragment를 호스팅하기 때문에 requireActivity()가 MainActivity가 된다.
class ProfileFragment : Fragment() {
private val userViewModel: UserViewModel by activityViewModels()
...
}
UserViewModel의 user Data는 LiveData를 통해 노출되고, login_fragment / profile_fragment 중 어디로 이동할지를 결정하려면 user Data를 observe해야 합니다.
ProfileFragment로 이동할 때 user Data가 있는 경우 앱에 환영 메시지가 표시됩니다.user Data 가 null이면 사용자가 프로필을 보기 전에 인증해야 하므로 LoginFragment로 이동합니다.
ProfileFragment에서 어디로 갈지 결정하는 로직을 작성한다.
class ProfileFragment : Fragment() {
private val userViewModel: UserViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val navController = findNavController()
userViewModel.user.observe(viewLifecycleOwner, Observer { user ->
if (user != null) {
showWelcomeMessage()
} else {
navController.navigate(R.id.login_fragment)
}
})
}
private fun showWelcomeMessage() {
...
}
}
ProfileFragment에 도달할 때 user data가 null이면 LoginFragment로 리디렉션됩니다.
NavController.getPreviousBackStackEntry() : previous destionation에 대한 NavBackStackEntry(destination 에 대한 NavController 관련 상태를 캡슐화하여 보관)를 가져올수 있음.
LoginFragment는 previous NavBackStackEntry의 SavedStateHandle을 사용하여 사용자가 성공적으로 로그인했는지 여부를 나타내는 초기 값을 설정합니다.
사용자가 즉시 system back button 누르면 반환하는 상태입니다.
SavedStateHandle을 사용하여 이 상태를 설정하면 프로세스가 종료된 후에도 상태가 유지됩니다
class LoginFragment : Fragment() {
companion object {
const val LOGIN_SUCCESSFUL: String = "LOGIN_SUCCESSFUL"
}
private val userViewModel: UserViewModel by activityViewModels()
private lateinit var savedStateHandle: SavedStateHandle
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
savedStateHandle = findNavController().previousBackStackEntry!!.savedStateHandle
savedStateHandle.set(LOGIN_SUCCESSFUL, false)
}
}
사용자가 id과 pw를 입력하면 인증을 위해 UserViewModel에 전달됩니다.
인증에 성공하면 UserViewModel은 사용자 데이터를 저장합니다.
그런 다음 LoginFragment는 SavedStateHandle의 LOGIN_SUCCESSFUL 값을 업데이트하고 back stack에서 popUp시킨다.
class LoginFragment : Fragment() {
companion object {
const val LOGIN_SUCCESSFUL: String = "LOGIN_SUCCESSFUL"
}
private val userViewModel: UserViewModel by activityViewModels()
private lateinit var savedStateHandle: SavedStateHandle
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
savedStateHandle = findNavController().previousBackStackEntry!!.savedStateHandle
savedStateHandle.set(LOGIN_SUCCESSFUL, false)
val usernameEditText = view.findViewById(R.id.username_edit_text)
val passwordEditText = view.findViewById(R.id.password_edit_text)
val loginButton = view.findViewById(R.id.login_button)
loginButton.setOnClickListener {
val username = usernameEditText.text.toString()
val password = passwordEditText.text.toString()
login(username, password)
}
}
fun login(username: String, password: String) {
userViewModel.login(username, password).observe(viewLifecycleOwner, Observer { result ->
if (result.success) {
savedStateHandle.set(LOGIN_SUCCESSFUL, true)
findNavController().popBackStack()
} else {
showErrorMessage()
}
})
}
fun showErrorMessage() {
// Display a snackbar error message
}
}
인증과 관련된 모든 논리는 UserViewModel 내에서 유지됩니다.
사용자 인증 방법을 결정하는 것은 LoginFragment 또는 ProfileFragment의 책임이 아니기 때문에 이것은 중요합니다.
ViewModel에서 로직을 캡슐화하면 공유하기 쉬울 뿐만 아니라 테스트하기도 더 쉽습니다.
navigation logic이 복잡한 경우 특히 테스트를 통해 이 논리를 확인해야 합니다.
ProfileFragment로 돌아가서 SavedStateHandle에 저장된 LOGIN_SUCCESSFUL 값은 onCreate() 메서드에서 관찰할 수 있습니다. 사용자가 ProfileFragment로 돌아오면 LOGIN_SUCCESSFUL 값이 확인됩니다.
값이 false이면 사용자를 MainFragment로 다시 리디렉션할 수 있습니다.
class ProfileFragment : Fragment() {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navController = findNavController()
val currentBackStackEntry = navController.currentBackStackEntry!!
val savedStateHandle = currentBackStackEntry.savedStateHandle
savedStateHandle.getLiveData<Boolean>(LoginFragment.LOGIN_SUCCESSFUL)
.observe(currentBackStackEntry, Observer { success ->
if (!success) {
val startDestination = navController.graph.startDestination
val navOptions = NavOptions.Builder()
.setPopUpTo(startDestination, true)
.build()
navController.navigate(startDestination, null, navOptions)
}
})
}
...
}
사용자가 성공적으로 로그인하면 ProfileFragment에 환영 메시지가 표시됩니다.
결과를 확인하는 데 사용된 기술을 사용하면 두 가지 다른 경우를 구별할 수 있습니다.
- 사용자가 로그인되어 있지 않고 로그인을 요청해야 하는 경우
- 사용자가 로그인하지 않기로 선택했기 때문에 로그인되지 않았습니다(false의 결과)
usecase를 잘 구분하면 사용자에게 반복적으로 로그인을 요청하는 것을 피할 수 있습니다.
'Android Jetpack Architecture > Navigation' 카테고리의 다른 글
Deep link for Destination (0) | 2021.11.29 |
---|---|
android Navigation (Destination 데이터 전달) (0) | 2021.11.29 |
navigation event listener (0) | 2021.11.26 |
Navigation and the back stack(네비게이션과 백 스택) (0) | 2021.11.19 |
Destination으로 이동하기. (0) | 2021.11.19 |