Slide 1

Slide 1 text

ESSENTIAL CONCEPTS TO KNOW WHEN LEARNING DECLARATIVE UI HYUNWOO LEE ORGANIZER, KOTLIN USER GROUPS SEOUL, SOUTH KOREA ANDROID/REACT NATIVE DEVELOPER, VIVA REPUBLICA(TOSS) DROIDKAIGI 2024

Slide 2

Slide 2 text

ESSENTIAL CONCEPTS TO KNOW WHEN LEARNING DECLARATIVE UI • WHAT IS “DECLARATIVE” UI? • HOW IT DIFFERS: IMPERATIVE UI VS DECLARATIVE UI • COMPONENTS OF DECLARATIVE UI PARADIGM • STATE MANAGEMENT: NOW AND THEN?

Slide 3

Slide 3 text

WHAT IS “DECLARATIVE” UI? HOW IT DIFFERS: IMPERATIVE UI VS DECLARATIVE UI

Slide 4

Slide 4 text

/ / Write the program ex) sums 1 to 10 THINKING IN “DECLARATIVE” WAY fun main() { var sum = 0 for (i in 1 . . 10) { sum += i } println(“sum = $sum”) }

Slide 5

Slide 5 text

/ / Write the program ex) sums 1 to 10 THINKING IN “DECLARATIVE” WAY fun main() { (1 . . 10).sum() println(“sum = $sum”) }

Slide 6

Slide 6 text

THINKING IN “DECLARATIVE” WAY HOW CAN I GET WHAT CAN I GET VS

Slide 7

Slide 7 text

THINKING IN “DECLARATIVE” WAY “HOW” CAN I GET - IMPERATIVE “WHAT” CAN I GET - DECLARATIVE VS

Slide 8

Slide 8 text

THINKING IN “DECLARATIVE” UI “HOW” CAN I DRAW WITH DATA “WHAT” CAN I DRAW WITH DATA VS

Slide 9

Slide 9 text

DECLARATIVE UI IS EVERYWHERE

Slide 10

Slide 10 text

COMPONENTS OF DECLARATIVE UI

Slide 11

Slide 11 text

SCREEN(COMPONENT) = SUM OF COMPONENTS FIRST OF ALL IS UI

Slide 12

Slide 12 text

COMPONENT - ANDROID FIRST OF ALL IS UI fun TimeTableItem( tags: List, title: String, isLike: Boolean ) { Column() { Row() { Tags(tags = tags) Like(isLike = isLike) } SectionTitle(title = title) } }

Slide 13

Slide 13 text

COMPONENT - IOS FIRST OF ALL IS UI struct TimeTableItem: View { var tags: [String] var title: String var isLike: Bool var body: some View { VStack { HStack { Tags(tags: tags) Like(isLike: isLike) } SectionTitle(title: title) } } }

Slide 14

Slide 14 text

COMPONENT-REACT NATIVE FIRST OF ALL IS UI function TimeTableItem({ tags, title, isLike }: Props) { return ( < / View> < / View> ) }

Slide 15

Slide 15 text

SCREEN = SUM OF COMPONENTS FIRST OF ALL IS UI • Single Screen(Single Component) = Sum of Multiple Components • Function Component vs Class Component • Function Component: Using data from parameters or local variables • Jetpack Compose(Android), React(React Native) • Class Component: Using data from the constructor, member variables • SwiftUI, React(React Native, old fashioned)

Slide 16

Slide 16 text

IMPORTANT BUT NOT IN HERE • Styling • Flex-way styling • List UI • Preview

Slide 17

Slide 17 text

STATE AND RENDER HOW TO RENDER AS I WANT? • So far, we have dealt with components using prede fi ned-data(parameter, constructor etc) • Then what should be done if the user needs to enter data or change the screen by user interactions?

Slide 18

Slide 18 text

STATE AND RENDER HOW TO RENDER AS I WANT? • “STATE” is the answer • Change of state triggers screen’s change -> re-render • Implementations of state and re-render in each libraries seem quite similar

Slide 19

Slide 19 text

STATE - ANDROID STATE AND RE-RENDER fun TimeTableItem( tags: List, title: String, isLike: Boolean ) { Column() { Row() { Tags(tags = tags) Like(isLike = isLike) } SectionTitle(title = title) } }

Slide 20

Slide 20 text

STATE - ANDROID STATE AND RE-RENDER fun TimeTableItem( tags: List, title: String ) { var isLike by remember { mutableStateOf(false) } Column() { Row() { Tags(tags = tags) Like( isLike = isLike, modifier = Modifier.clickcable { isLike = !isLike } ) } SectionTitle(title = title) } }

Slide 21

Slide 21 text

STATE - ANDROID STATE AND RE-RENDER fun TimeTableItem( tags: List, title: String ) { var isLike by remember { mutableStateOf(false) } Column() { Row() { Tags(tags = tags) Like( isLike = isLike, modifier = Modifier.clickcable { isLike = !isLike } ) } SectionTitle(title = title) } }

Slide 22

Slide 22 text

STATE - ANDROID STATE AND RE-RENDER fun TimeTableItem( tags: List, title: String ) { var (isLike, setIsLike) = remember { mutableStateOf(false) } Column() { Row() { Tags(tags = tags) Like( isLike = isLike, modifier = Modifier.clickcable { isLike = !isLike } ) } SectionTitle(title = title) } }

Slide 23

Slide 23 text

STATE - REACT NATIVE STATE AND RE-RENDER function TimeTableItem({ tags, title }: Props) { const [isLike, setIsLike] = useState(false); return ( setIsLike(!isLike)} / > < / View> < / View> ) }

Slide 24

Slide 24 text

STATE - REACT NATIVE STATE AND RE-RENDER function TimeTableItem({ tags, title }: Props) { const [isLike, setIsLike] = useState(false); return ( setIsLike(!isLike)} / > < / View> < / View> ) }

Slide 25

Slide 25 text

STATE - IOS STATE AND RE-RENDER struct TimeTableItem: View { var tags: [String] var title: String @State var isLike: Bool var body: some View { VStack { HStack { Tags(tags: tags) Like(isLike: isLike) } SectionTitle(title: title) } } } struct Like: View { @Binding var isLike: Bool var body: some View { if isLike { Image(.icFavoriteFill) } else { Image(.icFavoriteOutline) } } }

Slide 26

Slide 26 text

STATE AND RENDER STATE AND RE-RENDER • Re-render mechanisms are also similar • Responsibility for changing UI has shifted from the developer to the system.

Slide 27

Slide 27 text

STATE AND RENDER STATE AND RE-RENDER • Re-render mechanisms have similarities, but there are also slight differences • Android: Smart Recomposition • React: Virtual DOM • SwiftUI: Update by id

Slide 28

Slide 28 text

HANDLE NON-UI RELATED LOGIC IN UI CODE SIDE EFFECTS • How would you implement alert when like icon is displayed? • Although it is not directly related to rendering the UI, you must consider handling the operations necessary to prepare the data required for the UI. • Side Effect = Non-UI Rendering Related Logic, but ESSENTIAL Logics • HTTP Request, Permission Requests, Alarm Noti fi cation etc

Slide 29

Slide 29 text

SIDE EFFECTS fun TimeTableItem( tags: List, title: String ) { var isLike by remember { mutableStateOf(false) } Column() { Row() { Tags(tags = tags) Like( isLike = isLike, modifier = Modifier.clickcable { isLike = !isLike } ) } SectionTitle(title = title) } }

Slide 30

Slide 30 text

SIDE EFFECTS fun TimeTableItem( tags: List, title: String ) { var isLike by remember { mutableStateOf(false) } LaunchedEffect(isLike) { toast(if (isLike) “You stored this session!” else “You deleted this session”) } Column() { Row() { Tags(tags = tags) Like( isLike = isLike, modifier = Modifier.clickcable { isLike = !isLike } ) } SectionTitle(title = title) } }

Slide 31

Slide 31 text

SIDE EFFECTS HANDLING SIDE EFFECTS IN ANDROID • LaunchedEffect • DisposableEffect • SideEffect • etc

Slide 32

Slide 32 text

SIDE EFFECTS function TimeTableItem({ tags, title }: Props) { const [isLike, setIsLike] = useState(false); useEffect(() = > { / / Handle Side Effects alert(`${isLike ? ‘You stored this session’ : ‘You deleted this session’}`); }, [isLike]); return ( setIsLike(!isLike)} / > < / View> < / View> ); }

Slide 33

Slide 33 text

SIDE EFFECTS function TimeTableItem({ tags, title }: Props) { useEffect( () = > { / / Handle Side Effects }, [isLike] ); }

Slide 34

Slide 34 text

SIDE EFFECTS function TimeTableItem({ tags, title }: Props) { useEffect( () = > { / / Handle Side Effects }, [isLike] ); } Denpendency Array • Empty Array: if screen mounts, callback executes once(actually not) • Non-empty Array: if element’s value change, callback executes • No-value(unde fi ned): Callback executes at every re-render

Slide 35

Slide 35 text

SIDE EFFECTS function TimeTableItem({ tags, title }: Props) { useEffect( () = > { / / Handle Side Effects () = > { } }, [isLike] ); } Clean Up Function • Executes when the component unmounts on screen

Slide 36

Slide 36 text

SIDE EFFECTS HANDLING SIDE EFFECTS IN ANDROID • LaunchedEffect • DisposableEffect • SideEffect • etc

Slide 37

Slide 37 text

SIDE EFFECTS HANDLING SIDE EFFECTS IN ANDROID • LaunchedEffect • keys: similar as “dependency array” • but if you want to execute only once when component mounts, you need to put unchangeable value (Unit, true) • Side Effect: de fi ne your side effect (suspend function) LaunchedEffect(key1, key2) { / / Side Effect }

Slide 38

Slide 38 text

SIDE EFFECTS HANDLING SIDE EFFECTS IN ANDROID • DisposableEffect • keys: same as LaunchedEffect • Side Effect: non-suspend function • onDispose: executes when component unmounts DisposableEffect(key1, key2) { / / Side Effect onDispose { } }

Slide 39

Slide 39 text

SIDE EFFECTS HANDLING SIDE EFFECTS IN ANDROID • SideEffect • Executes every re-render SideEffect { / / Side Effect }

Slide 40

Slide 40 text

SIDE EFFECTS HANDLING SIDE EFFECTS IN ANDROID • LaunchedEffect • DisposableEffect • SideEffect • etc

Slide 41

Slide 41 text

SIDE EFFECTS HANDLING SIDE EFFECTS IN SWIFTUI • onAppear • onDisappear • onChange

Slide 42

Slide 42 text

SIDE EFFECTS HANDLING SIDE EFFECTS IN SWIFTUI VStack { Text(text).padding() Button("Change Text") { text = "Text changed \(count + 1) times" } .onAppear { } .onDisappear { } .onChange(of: text) { newValue in print("Text changed to: \(newValue)”) } }

Slide 43

Slide 43 text

GOOD, THEN IS IT ENOUGH? • With this level of knowledge, you should be able to create components or screens with simple logic using declarative UI. • However, if the logic becomes even slightly more complex or the screen has multiple layers, the readability & developer experience can signi fi cantly decrease.

Slide 44

Slide 44 text

SHARE THE STATE TO BOTTOM NODE • How to share the ui state or theme data(color, typography etc) to child components. • Using parameters (props drilling) • Local scoped data (like data teleportation)

Slide 45

Slide 45 text

SHARE THE STATE TO BOTTOM NODE • Local Scoped Data in Jetpack Compose - CompositionLocal • staticCompositionLocalOf • Suppose CompositionLocal’s value does not change • If value changes entire screen will be recomposed • compositionLocalOf • If value changes, recompose components that read value

Slide 46

Slide 46 text

SHARE THE STATE TO BOTTOM NODE • Local Scoped Data • In SwiftUI - EnvironmentalValue • React - Context

Slide 47

Slide 47 text

SHARE THE STATE TO BOTTOM NODE / / Define shared data val LocalColor = staticCompositionLocalOf { Error(“No Colors”) } LOCAL SCOPED DATA(COMPOSITIONLOCAL) - JETPACK COMPOSE / / Provide value to “LocalColor” (CompositionLocal) CompositionLocalProvider( LocalColor provides appColors() ) { / / The components within this block can use LocalColors }

Slide 48

Slide 48 text

SHARE THE STATE TO BOTTOM NODE LOCAL SCOPED DATA(COMPOSITIONLOCAL) - JETPACK COMPOSE CompositionLocalProvider( LocalColor provides appColors() ) { KaigiRow() } fun KaigiRow() { / / Use LocalColors in here val colors = LocalColors.current Row(modifier = Modifier.background(color.background)) { } }

Slide 49

Slide 49 text

SHARE THE STATE TO BOTTOM NODE LOCAL SCOPED DATA(ENVIRONMENTVALUES) - SWIFT / / Define key struct ThemeKey: EnvironmentKey { static let isDarkMode: Bool = false } / / Introduce new value to EnvironmentValues extension EnvironmentValues { var isDarkMode: Bool { get { self[ThemeKey.self] } set { self[ThemeKey.self] = newValue } } }

Slide 50

Slide 50 text

SHARE THE STATE TO BOTTOM NODE LOCAL SCOPED DATA(ENVIRONMENTVALUES) - SWIFT / / Provide EnvironmentValues struct ContentView: View { var body: some View { VStack { Child() .environment(\.isDarkMode, true) } } } / / Use EnvironmentValues struct Child: View { @Environment(\.isDarkMode) var isDarkMode }

Slide 51

Slide 51 text

SHARE THE STATE TO BOTTOM NODE LOCAL SCOPED DATA(CONTEXT) - REACT / / Define Context const ThemeContext = createContext() / / Define Context Provider const ThemeProvider = ({ children }) = > { const [theme, setTheme] = useState(‘light’) return ( {children} < / ThemeContext.Provider> ) }

Slide 52

Slide 52 text

SHARE THE STATE TO BOTTOM NODE LOCAL SCOPED DATA(CONTEXT) - REACT / / Define component that use context function Component() { const { theme } = useContext() return {`Current Theme is ${theme}`} < / Text> } / / Wrap it with Provider function Component() { return < / ThemeProvider> }

Slide 53

Slide 53 text

FUTURE OF STATE MANAGEMENT INTRODUCTION OF ASYNC(SERVER) STATE MANAGEMENT

Slide 54

Slide 54 text

MVI, TCA AND REDUX STATE MANAGEMENT WITH STORE • MVI, TCA is “hot trend” for developers using Compose, SwiftUI • Is it really “trendy” way? • Managing the UI State in a single store(or N store) has already been demonstrated in React’s Flux pattern(Redux)

Slide 55

Slide 55 text

MVI | TCA | REDUX

Slide 56

Slide 56 text

ASYNC(SERVER) STATE MANAGEMENT • SWR - stale-while-revalidate • Use existing cache before response arrived • SWR’s ef fi ciency 👍 - just manage your options SYNC MORE EASILY WITH SERVER STATE - SWR

Slide 57

Slide 57 text

ASYNC(SERVER) STATE MANAGEMENT • Async state management - Data (from external source) management + SWR strategy • Actually screen state is closely related to server state(loading, success, etc) • Async State Management explains “we should manage also this state too!” • Example - Optimistic Update • Assume that the server response is successful and maintain the UI state accordingly, but roll it back if the request fails SYNC MORE EASILY WITH SERVER STATE - ASYNC STATE MANAGEMENT

Slide 58

Slide 58 text

ASYNC(SERVER) STATE MANAGEMENT S-W-R(STALE-WHILE-REVALIDATE) & REACT QUERY • Most famous async state management library • There are many features • Caching • Updating stale data when it stales (using staleTime) • Invalidate cache forcefully • Provide loading state, error state to draw screen more easily using those function Example() { const {data, isLoading, isError, refetch} = useQuery(‘index’, fetchData, { staleTime: 5000 } ); if (isLoading) return Loading… < / Text> if (isError) return < / View> const invalidate = () = > { queryClient.invalidateQueries(‘index’) } return {data}Invalidate < / View> < / View> }

Slide 59

Slide 59 text

CONCLUSION • "Declarative UI is a concept that is not tied to any speci fi c library. • If you understand the essential concepts, you can work with other libraries as well and write code with a certain level of pro fi ciency. • If you're considering about how to effectively use declarative UI, I recommend looking into the methodology in React. • React uses many concepts like React Hooks and local state management, which I couldn't introduce(due to time constraints). • These approaches were developed by those who faced similar challenges years ahead of us as Android developers, so they can become best practices for the services you're currently managing.

Slide 60

Slide 60 text

THANKS!! HyunWoo Lee Toss(Viva Republica) | South Korea Android (React Native) Developer