Android App architecture: UI events

안드로이드 App architecture: UI events를 알아보겠습니다.




출처:
https://developer.android.com/topic/architecture/ui-layer/events


UI(유아이) events(이벤트)는 UI layper에서 관리되어야 합니다. UI 또는 ViewModel에서 말이죠. 가장 기초적인 events의 종류는 user(사용자) events입니다. user는 user events를 앱과 상호작용하며 생성합니다. 예를 들면 화면을 누르거나 손가락 행동을 통해서 말이죠. UI에서는 이러한 events를 onClick()으로 사용합니다.



• 용어:

  - UI: View-based or Compose에서 사용자와 상호작용하는 코드

  - UI events: UI layer에서 관리되어야 하는 행위들

  - User events: user가 앱과 상호작용하면서 만드는 events



ViewModel은 보통 user event에서 business(비즈니스) logic(논리)을 다루는 역할을 합니다. 예를 들면, data(데이터)를 다시 불러오는 버튼을 누르는 것이 있습니다. 보통 ViewModel은 이러한 함수를 외부에 드러내어 UI가 해당 함수를 부를 수 있게 해줍니다. User events는 UI behavior(행동) logic을 포함할 수 있습니다. 예를 들면, 다른 화면으로 화면을 전환하거나 Snackbar(스낵바 알림창)를 보여주는 행위가 있습니다.



반면에 business logic은 어떠한 기기를 사용하든 같은 앱에선 동일합니다. UI behavior logic은 아래의 경우 다른 동작을 할 수 있습니다. UI layer(레이어) 문서에서는 logic을 이렇게 정의합니다.



• Business logic: state(상태)가 변경될 때, 무엇을 할 것인가. 예를 들면, 결제나 user 설정을 저장하는 것이 있습니다. 보통 domain과 data layers는 이러한 logic을 다룹니다. Architecture Components ViewModel의 지침을 볼 때, ViewModel 또한 business logic을 다룰 수 있다고 주장할 수 있습니다.

• UI behavior logic 또는 UI logic: state가 변할 때, 어떻게 화면에 표현할 것인가. 예를 들면, navigation(화면 전환) logic 또는 메시지를 어떻게 user에게 보여줄 것인가가 있습니다. UI가 이 logic을 다룹니다.



• 메모: 

여기서 추천하는 것들은 앱을 확장 가능하고 품질을 향상하고 좀 더 역동적으로 만듭니다. 그리고 테스트하기 쉽게 만듭니다. 하지만, 어디까지나 지침일 뿐 당신의 상황에 맞게 사용하셔야 합니다.





⊙ UI event decision tree: UI event 결정 도표;

아래의 도표는 가장 좋은 결정을 도와주는 도표입니다. 아래에서 좀 더 자세히 다뤄보겠습니다.






⊙ Handle user events: user events 관리;

UI 요소의 state를 변경하는 user events는 UI가 직접 관리할 수 있습니다. 예를 들면, 목록 내용을 확장하는 경우가 있습니다. 만약 user event가 화면의 data 최신화같이 business logic이 필요하다면, ViewModel에서 관리할 수 있습니다.



아래의 예시는 목록 내용을 확장하는 logic(UI logic)과 화면의 data를 최신화하는 logic(business logic)의 경우가 포함되어 있습니다.







• User events in RecyclerViews: RecyclerViews에서의 User event;

만약 사용자의 행위가 RecyclerView의 목록 내용이나 custom(커스텀) 된 View처럼 UI tree(트리)에서 깊숙한 부분까지 내려간다면 ViewModel이 user events를 다루는 것이 좋을 수 있습니다.



예를 들면, NewsActivity에 책갈피 버튼이 있는 뉴스 목록 항목이 있는 것을 생각해 봅시다. ViewModel은 책갈피가 설정된 뉴스의 ID를 알고 있어야 합니다. 사용자가 뉴스에 책갈피 설정을 할 때 RecyclerView의 adapter에서 ViewModel의 addBookmark(newId)를 사용하지 않습니다. 대신에 ViewModel에서 NewsItemUiState를 참조합니다.







이 방법은 RecyclerView adapter(어댑터)가 필요한 data와 상호작용하는 방식입니다. NewsItemUiState list(리스트)를 사용해서 말이죠. adapter는 ViewModel 전체에 접근할 필요가 없습니다. ViewModel의 노출을 최소화시킵니다. ViewModel을 activity에서만 사용할 수 있게 해서 반응성을 나눌 수 있습니다. 이렇게 하면 views나 RecyclerView adapters 같은 UI-specific object가 직접적으로 ViewModel과 상호작용하는 것을 보호합니다.



• 경고:

RecyclerView adapter에 ViewModel을 넘기는 것은 좋지 않습니다. ViewModel class와 adapter가 강력하게 결합되기 때문입니다.



• 메모:

다른 방법으로는 Callback을 RecyclerView adapter에 사용하는 것입니다. 이러한 경우 activity 또는 fragment가 Callback을 관리하거나 ViewModel이 직접적으로 관리할 수 있게 됩니다.







⊙ Naming conventions for user event functions: User events의 작명 규칙;

이 지침에서는 user events를 다루는 ViewModel 함수는 그들이 행하는 기능의 verb(동사)를 기반으로 작명합니다. 예를 들면, addBookmark(id) 또는 logIn(username, password)가 있습니다.





⊙ Handle ViewModel events: ViewModel events 관리:

ViewModel에서 발생한 UI actions를 ViewModel events라 부르며, 항상 UI state를 최신화합니다.

이러한 이유는 Unidirectional Data Flow(단방향 데이터 흐름)를 따르기 위해서입니다. 설정이 변경되거나 UI actions가 유실되지 않지 않고 다시 생성되게 만듭니다. 선택적으로 앱이 종료된 후, events가 다시 재생성 되도록 만들 수 있습니다. Saved state module의 문서를 봐주세요.



UI actions를 UI state로 변경하는 일은 항상 간단하지 않습니다. 그러나 간단하게 만들 수도 있습니다. 특정한 화면으로 이동하는 방법에 대해 고민하는 것이 아니라 더 멀리 바라보고 user flow를 UI state에 잘 녹일지를 생각해야 합니다. 다른 말로 하자면, 어떠한 actions가 UI를 만드는데 필요한가 보다 이러한 actions가 UI state에 어떤 영향을 줄까를 생각해야 합니다.



• 중요 사항:

ViewModel events는 항상 UI state를 최신화시킵니다.



예를 들면 user가 로그인 화면에서 로그인을 완료하였을 때의 화면 전환 상황입니다. 그러면 당신의 UI state는 이렇게 생성될 수 있습니다.






이 UI는 isUserLoggedIn의 state에 따라 올바른 화면으로 화면 전환을 합니다.






• 메모:

이 코드 예시는 coroutines와 how to use them with lifecycle-aware components의 이해가 필요합니다.




• Consuming events can trigger state updates: Events를 소비하여 state 최신화하기:

ViewModel events를 UI에서 소비하면 다른 UI state가 업데이트될 수 있습니다. 예를 들어, 짧게 보이는 메시지를 화면에 보여주면 user는 뭔가 일어난 것을 알 수 있습니다. UI는 이러한 메시지가 보일 때, ViewModel의 다른 state를 최신화가 필요할 수 있습니다. 이 event는 user가 메시지를 사라지게 만들거나 시간 초과로 사라지는 등의 소비를 하게 되면 발생합니다. 그리고 이러한 메시지를 사라지게 하는 행위는 user 입력으로 간주되어 처리됩니다. ViewModel은 이러한 것을 알아채야 합니다. 이러한 상황에서 UI state는 아래와 같이 정의될 수 있습니다.







ViewModel은 아래의 business logic으로 새로운 message를 사용자에게 보여줄 수 있습니다.







ViewModel은 UI가 어떻게 message를 화면에 보여주는지 알 필요가 없습니다. 단지 user message를 화면에 보여줄 필요가 있다는 것을 알뿐이죠. message가 화면에 보일 때, UI는 ViewModel에게 다음 message를 위해 userMessage property(속성)을 초기화 시킬 필요가 있다는 것을 알려줘야 합니다.





비록 message가 짧은 시간 동안 보이더라도 UI state는 충실하게 화면에 무엇을 보여줄지에 관한 상태를 보유해야 합니다. 비록 message가 화면에 보이든 아니든 말이죠.



• 메모:

좀 더 어려운 사례인 user message 목록을 보여주는 경우는 Jetsnack Compose sample을 참고해 주세요.







⊙ Navigation events: 화면전환 events;

Consuming events can trigger state update 항목에서 어떻게 UI state가 user messages를 화면에 띄우는지 알아보았습니다. Niavigation events 역시 흔한 Android events입니다.



UI에서 user가 버튼을 눌러 event가 발생하게 되면, navigation controller 또는 적절한 composable 함수를 불러서 UI가 처리합니다.







화면 전환 전에 data 입력에 대한 확인 작업이 필요하다면, ViewModel은 해당 state를 UI로 노출하는 것이 필요합니다. UI는 적절하게 state 변화에 반응하며 화면전환을 이룹니다. The Handle ViewModel events 영역에서 다룬 사례와 비슷합니다. 아래는 비슷한 코드입니다.






위의 예시는 앱이 예상한 대로 작동합니다. 왜냐하면 현재 화면인 Login 화면은 backstack(백 스택)에 저장하지 않기 때문입니다. Users는 뒤로 가기를 눌러도 Login으로 돌아가지 않습니다. 그러나 Login으로 돌아가야 하는 상황에서는 위의 코드에서 몇 가지 logic이 필요합니다.







• Navigation events when the destination is kept in the back stack: 현재 화면이 뒤로 가기로 갈 필요성이 있는 화면전환;

ViewModel의 state 변화가 화면 A와 화면 B로 전환할 때, 화면 A를 계속 저장한다면 A가 저장되지 않게 추가적인 logic이 필요합니다. state를 만들어서 UI가 다른 화면으로 갈 때, backstack에 넣어야 하는지를 관리할 수 있습니다.  보통 이러한 state는 UI에서 관리합니다. 왜냐하면 Navigation logic은 UI가 관리하지 ViewModel에서 관리하지 않기 때문입니다. 이것을 설명하기 위해 아래의 사례를 보여드립니다.



앱에서 회원가입 절차에 있다고 생각해 봅시다. 생일 확인 화면에서 user가 날짜를 입력하고 continue를 누를 때, ViewModel에서 확인합니다. ViewModel은 해당 확인 logic을 data layer로 전달합니다. 만약 날짜가 올바르다면 user는 다음 화면으로 갑니다. 추가적인 사항으로는 user는 입력한 정보 수정을 위해 언제든지 뒤로 갈 수 있습니다. 그러므로 회원가입의 모든 현재 화면은 같은 back stack에서 관리되어야 합니다. 이러한 요구사항을 만족하기 위해 아래와 같이 정의할 수 있습니다.







날짜의 검증은 business logic으로 ViewModel에서 관리합니다. 그리고 대부분 ViewModel은 해당 작업을 data layer로 넘깁니다. 다음 화면으로 사용자를 보내는 logic은 UI logic입니다. 왜냐하면 이러한 변경사항은 UI 설정 사항이 변경되기 때문입니다. 예를 들어, 태블릿에서 여러 등록 화면을 보여주어 다음으로 자동으로 넘어가지 않게 하고 싶을 때, validationInProgress 변수는 다음 화면으로 넘어갈지 말지를 결정할 수 있습니다. 사용자가 데이터를 입력하고 다음으로 버튼을 누르더라도 말이죠.





⊙ Other use cases: 다른 사례;

만약 당신이 UI event가 UI state 최신화를 하지 못한다고 생각한다면, data의 흐름을 다시 생각해 보세요. 아래의 원칙들이 있습니다.

  • Each class should do what they're responsible for, not more: class는 그들이 맡은 역할만 해야 합니다;

UI는 sceen-specific behavior logic만 다룹니다. 화면 전환 요청이나 터치, 권한 요청 등이 있습니다. ViewModel은 business logic을 가지고 깊숙한 곳의 UI 결과를 UI state로 전환합니다.

  • Think about where the event originates: Event가 어디서 생성된 것인지 생각하기;

이 지침의 첫 장에 있는 decision tree 도표를 따르고, 각각의 class가 어떤 역할을 하는지 결정합니다. 예를 들어, event가 UI에서 발생하고 화면전환을 발생시킨다면, UI에서 관리되어야 합니다. 어떠한 logic은 ViewModel에서 다뤄야 합니다만, 모든 event가 ViewModel에서 다뤄질 필요는 없습니다.

  • If you have multiple consumers and you're worried about the event being consumed multiple times, you might need to reconsider your app architecture: 여러 곳에서 event가 사용되거나 반복적으로 사용되는 것이 걱정된다면 구조를 변경해야 합니다;

여러 곳에서 동시다발적으로 event가 사용된다면 같은 것을 사용하는지 보장하기 어렵습니다.  복잡성이 증가하고 보이지 않는 행동이 증가합니다. 만약 당신이 이러한 문제를 가지고 있다면, UI tree의 상단으로 event를 보낼 필요가 있습니다.

  • Think about when the state needs to be consumed: 언제 state가 소비될지 생각하기:

앱이 background에 있을 때, state가 계속해서 소비되기를 원하지 않을 수 있습니다. 예를 들면, Toast(짧은 시간 메시지 표시)가 있습니다. 이러한 경우 state를 foreground 일 때에만 소비하는 것을 고려할 수 있습니다.





• 메모:

몇몇 앱은 Kotlin Channels나 reactive streams를 사용하여 ViewModel events를 UI에서 처리하도록 하는 걸 봤을 수 있습니다. 이러한 생성자(ViewModel)이 UI(Compose 또는 Views)보다 오래 살 경우 이러한 해결책은 올바르게 작동한다고 보장할 수 없습니다. 개발자에게 미래의 알 수 없는 문제를 제공할 수 있습니다. 그리고 user에게 절대로 제공해서는 안 되는 state 불일치, 오류, 정보 손실 등이 일어날 수 있습니다.

만약 당신이 이러한 상황 중에 하나라면 당신의 UI에서 one-off ViewModel event에 대해 생각해 보아야 합니다. 즉각적으로 관리하고 UI state에 크기를 줄여서 관리합니다. UI state는 UI를 표현할 수 있는 좋은 기술입니다. data가 전달되고 처리되는 것을 보장합니다. 그리고 테스트하기 쉽고, 다른 앱의 부분들과 상호작용하기 좋습니다. 

왜 위에서 언급한 Kotlin Channels나 reactive streams가 적합하지 않는지 알고 싶다면 ViewModel: One-off event antipatterns 블로그 글을 읽어주세요.





끝.


카테고리: Android


댓글

이 블로그의 인기 게시물

Python urllib.parse.quote()

Python OpenCV 빈 화면 만들기

tensorflow tf.random.uniform()

Android Notification with Full Screen

KiCad 시작하기 2 (PCB 만들기)

Android Minimum touch target size

Python bs4.SoupStrainer()

KiCad 시작하기 4 (기존 회로도 수정 및 추가)

음악 총보(Score), 파트보(Part)

tensorflow tf.expand_dims()