Xamarin, MAUI๋ ์๋์ง๋ง ํฌ๋ก์ค ํ๋ซํผ์ด๋ผ ์ฌ๊ธฐ์ ๊ณต์ ํฉ๋๋ค.
์ฌ๋ด ํ๋ก๊ทธ๋จ์ด๋ผ ์ด๋ฏธ์ง ๋ฐ ์์ค๋ฅผ ๊ณต๊ฐํ์ง ๋ชปํ๋ ์ ์ํด ๋ถํ๋๋ฆฝ๋๋ค.
์ฌ๋ด CCU(Concurrent connected User) ๋ชจ๋ํฐ๋ง ํ๋ก๊ทธ๋จ์ AvaloniaUI 0.10.18 ๋ฒ์ ๊ณผ Community Toolkit 8 ๋ฒ์ ์ผ๋ก ์์ ์ ํ์์ต๋๋ค.
AvaloniaUI(์ดํ Avalonia)๋ฅผ 0.10.18 ๋ฒ์ ์ ์ ํํ๋ ์ด์ ๋, ํ์ฌ Avalonia 11.0.0-Preview ๋ฒ์ ์์ Community Toolkit 8์ด ๋น๋ ์๋ฌ๋ฅผ ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ด๋ฒ์ Avalonia๋ฅผ 11 preview2 ๋ฒ์ ์ผ๋ก ์ฌ๋ฆฌ๋ฉด์ Reactive UI๋ ํจ๊ป ๊ฐ์ ๋ฒ์ ์ผ๋ก ์ ์ฉํด๋ดค์ต๋๋ค.
Reactive UI๋ฅผ ๊ณต๋ถํ๋ ๊ฒฝํ์ ๋ฉ๋ชจ ํ์๋๋ฐ ์๋ณธ์ ๊ณต์ ๋๋ฆฝ๋๋ค.
Avalonia UI RxUI ๋ฉ๋ชจ
-
RxUI ๋ฌธ์์ Command๋ฅผ ๋ฐ์ธ๋ฉํ ๋ Code-Behind๋ฅผ ์ฌ์ฉํ๋๋ก ๋์ด์์ง๋ง, XAML์ ํตํด ๋ฐ์ธ๋ฉํ ์ ์๋ค.
-
WhenActivated๋ View๋ง๋ค ํด์ค์ผํ๋ ๊ฒ ๊ฐ๋ค? (ํ์คํ์ง ์์)
View์ Code-Behind์์ ๋ฐ์ธ๋ฉํ์ง ์๊ณ , XAML์์ ๋ฐ์ธ๋ฉํ ๊ฒฝ์ฐ์ ํ์ ์๋ ๊ฒ ๊ฐ๋ค. -
ViewModel์ ํ์ฑํ ์์ ์ด ์ด๋ค ๋์ธ์ง๋ ๋ชจ๋ฅด๊ฒ ์ผ๋, ViewModel์์ ํ์ฑํ๊ฐ ๋ ๋ ์ด๋ฒคํธ ์บ์นํ๋ ค๋ฉด IActivatableViewModel ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ค์ผํ๋ค.
ํด๋น ์ธํฐํ์ด์ค๋ฅผ ViewModel์ ๊ตฌํํ๋ฉด WhenActivated๋ฅผ ViewModel์์๋ ์ฌ์ฉํ ์ ์๋๋ฐ,
์ด ์ฝ๋ ๋ธ๋ญ ์์ ์ฝ๋๋ View ์ชฝ์์ WhenActivated๋ฅผ ํธ์ถํ ๋ ๊ฐ์ด ํธ์ถ๋๋ค. -
RxUI์์ ReactiveWindow์ Window๋ฅผ, ReactiveUserControl์ UserControl ์ ํผ์ฉํด์ ์ฌ์ฉํ ์ ์๋ค. ํ์ง๋ง ๊ทธ๋ฅ ReactiveWindow, ReactiveUserControl์ ์ฌ์ฉํ๋ ๊ฒ์ผ๋ก ํต์ผํ์.
-
4๋ฒ ๋๋ฌธ์ RxUI์์๋ View์ ViewModel์ ์๋ก ์์กด์ํฌ ์ ๋ฐ์ ์๋ ๊ฒ ๊ฐ๋ค.
-
์์กด์ฑ ๋ฑ๋ก์ ํ ๋๋ Splat์ ์ฌ์ฉํ๋๋ฐ, ๋ฌธ์๋ณด๊ณ ์ ๋ฐ๋ผํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
์๋ ๋ฌธ์์ ์ ํ์๋ฏ, ์๋น์ค๋ฅผ ๋ฑ๋กํ ๋๋ CurrentMutable์ ์ฌ์ฉํ๊ณ ๊ฐ์ฒด๋ฅผ ๊บผ๋ด์ฌ ๋๋ Current๋ฅผ ์ด์ฉํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
reactiveui - Splat: Locator.Current vs Locator.CurrentMutable - Stack Overflow
๋์ฒ๋ผ Generic Host๋ฅผ ์ด์ฉํ ๊ฒฝ์ฐ ์๋ ๋ฌธ์๋๋ก ํ๋ค.
splat/src/Splat.Microsoft.Extensions.DependencyInjection/README.md at main ยท reactiveui/splat ยท GitHub -
ViewModel์ Splat์ ๋ฑ๋กํ ๋๋ IViewFor ์ธํฐํ์ด์ค๋ฅผ ์ด์ฉํด์ ํด๋น ๋ทฐ๋ชจ๋ธ์ด ์ด๋ค ๋ทฐ์ ๋ทฐ๋ชจ๋ธ์ธ์ง ๊ฐ์ ์ ์ผ๋ก ์ง์ ํ๋ค.
IViewFor๋ ๋ฐ๋์ ํ ํ์๋ ์๊ณ , ViewModel์ ๋ํด์ View๊ฐ ์ฌ๋ฌ๊ฐ๊ฐ ์์ ๋ Locator๋ก ๋งค์นญํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ, Customํ๊ฒ View์ ๋ํ ViewModel์ ์ง์ ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
๋ฐ๋ผ์ ์ผ๋ฐ์ ์ผ๋ก๋ ํ์์๊ณ Naming ๊ท์น์ ์ ๋ฐ๋ฅธ๋ค๋ฉด ์ฐ์ง ์์๋ ๋๋ค.
ํน์ View๋ฅผ ํ์ฅํ์ฌ ๊ทธ ํ์ฅ๋ view์ ViewModel์ ์ง์ ํ๋ ๊ฒฝ์ฐ์๋ ์ฌ์ฉ๋๋ค. -
Converter๋ฅผ ๊ตฌํํ ๋๋ IValueConverter ์ธํฐํ์ด์ค๊ฐ ์๋๋ผ IBindingTypeConverter๋ฅผ ๊ตฌํํด์ผ ํ๋ค. Converter ์์
์ฌ์ค IValueConverter๋ก ๊ตฌํํด์ XAML์ ๋ฐ์ธ๋ฉ์ด ๊ฐ๋ฅํด ๋ณด์ด์ง๋ง,
RxUI์ Code-Behind Binding์ ํ๋ฉด์ Converter๋ฅผ ์ ์ฉํ๋ ค๋ฉด IBindingTypeConverter๋ฅผ ์ด์ฉํด์ผํ๋ ๊ฒ ๊ฐ๋ค.
๊ทธ๋ฆฌ๊ณ Locator.CurrentMutable.RegisterConstant(new MyCoolTypeConverter(), typeof(IBindingTypeConverter)); ์ด๋ ๊ฒํ๋ฉด Splat์ ๋ฑ๋กํด์ IoC ๋ฐฉ์์ผ๋ก๋ ์ด์ฉ๊ฐ๋ฅํ๋ค.
ํ์ง๋ง ์ญ์, ์ผ๋ฐ์ ์ธ IValueConverter๋ฅผ ์ด์ฉํด์๋ ํ๋ก๊ทธ๋จ์ด ๋์ํ๋๋ฐ ์ค๋์์ ์๋ค. -
๋ฐ์ดํฐ ์์์ฑ์ ์ผ์ข ์ ์บ์ฑ๊ธฐ๋ฅ์ด๋ค. App์ ๊ป๋ค๊ฐ ์ผ๋ ๋ฐ์ดํฐ๊ฐ ์ ์ง๋๋ ๊ฒ์ด๋ผ๊ณ ๋ณด๋ฉด ๋๋ค. (์๋ฆฌ๋ ์ ๋ชจ๋ฅด๊ฒ ์)
DataContractAttribute์ DataMemberAttribute๋ฅผ ์ด์ฉํด์ ์ ์ฅํ ๊ฐ์ ์ง์ ํ๋ค. -
WhenActivated ๋ฌธ์๋ฅผ ๋ณด๋ฉด WhenActivated๋ฉ์๋์ Action ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ์ธ disposables์ ์ด์ฉํ์ฌ View๊ฐ ์ ๊ฑฐ๋ ๋ View์ ์ฐ๊ฒฐ๋ ViewModel์ ๊ด์ฐฐ๊ฐ๋ฅ Property์ ๊ด์ฐฐ๊ฐ๋ฅ Command๋ฅผ ์ ๊ฑฐํ ์ ์๋ค.
-
Interaction ํด๋์ค๋ input viewmodel๊ณผ output viewmodel์ ์ ๋ค๋ฆญ์ผ๋ก ์ง์ ํ ์ ์๋ค.
์ด๊ฒ์ ๋ํ์ฐฝ ๊ฐ์ UI์ ์ํธ์์ฉ์ ํ ๋ ์ฐ์ด๋ ๊ฒ์ผ๋ก ๋ค๋ฅธ ํ๋ ์์ํฌ๋ค์์ DialogService๋ฅผ ๊ตฌํํด์ ํธ์ถํ๋๋ฐ, rxui์์๋ ViewModel๊ฐ ๊ด๊ณ๋ฅผ ๋งบ์ด์ฃผ๊ณ
Interaction๊ฐ์ฒด์์ Handle ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด์ input viewmodel์ ๋ฃ์ด์ ํธ์ถ ๋ฐฉํฅ์ผ๋ก ํ๊ณ ์๋ค.
Handle ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด Code-Behind์์ ๊ตฌ๋ ํ ๋ฉ์๋๊ฐ ํธ์ถ๋๋ค.
์์ ๋ฐฉ๋ฒ์ Avalonia.MusicStore ์ํ ์ฑ์ MainWindowViewModel์ BuyMusicCommand์์ ShowDialog.Handle์ ํธ์ถํ์ ๋,
๋ค์ ์ค๋ก ๋์ด๊ฐ๊ธฐ ์ Handle ๋ฉ์๋๋ก ํธ์ถ๋๋ ๊ฒ์ด ๋ฌด์์ธ์ง ํ์ ํ ์ ์๋ค.
MainWindow.axaml.cs์ DoShowDialogAsync ๋ฉ์๋๋ฅผ ํธ์ถํ๊ฒ๋๋๋ฐ ์ด๋ MainWindow์ ์์ฑ์์์ ViewModel์ Interaction ๊ฐ์ฒด์ธ ShowDialog์ ๊ตฌ๋ ๋ฉ์๋๋ก
DoShowDialogAsync๋ฅผ ๋ฑ๋กํด๋จ๊ธฐ๋๋ฌธ์ด๋ค.
ํด๋์ค๊ฐ์ ๊ด๊ณ๊ฐ ์ฝ๋๋ก ๋ช ์๋์ด์์ด์ ์ง๊ด์ ์ด๋ค. -
๋ฐ๋ณต๋ ์์ ์ด๋ ๋ฌด๊ฑฐ์ด ์์ ์ ๊ฒฝ์ฐ ์ค์ผ์ฅด๋ง์ ์ด์ฉํ๋ฉด ๋๋ฉฐ ์๋ ๊ธ์ ์ ๋ํ๋ ์๋ค.
| An advanced, composable, reactive model-view-viewmodel framework
์๋ ๊ธ์ ๋ณด๋ฉด Invoke๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ RxUI ๋ฐฉ์์ผ๋ก ๊ต์ฒดํ ์ ์๋ค.
| An advanced, composable, reactive model-view-viewmodel framework -
๋ฐ๋ณต๋ ๊ฐ์ด ํ์ธ๋ ๊ฒฝ์ฐ ์ค๋ณต ์ ๊ฑฐ๋ DistinctUntilChanged ๋ฉ์๋๋ฅผ ํตํด ํด๊ฒฐํ ์ ์๋ค.
-
Subscribe ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ํด๋น ํ๋กํผํฐ์ ์ด๋ค ์์ ์ด ๋ฐ์ํ ๋๋ง๋ค ๊ตฌ๋ ์ ๋ฑ๋กํ Action ๋ฉ์๋๊ฐ ๋ฐ์ํ๋ ๊ฒ์ธ๋ฐ, ToProperty๋ฅผ ์ฌ์ฉํ๋ฉด
๋ฐ์ดํฐ๋ฅผ ํน์ ํ๋กํผํฐ๋ก ์ ์๋ ์๋ค. -
WhenAny ๋ฉ์๋๋ ํน์ ์์ฑ๊ฐ์ ๊ฒ๋ค์ด ๋ณ๊ฒฝ๋์์ ๋ ๊ทธ๊ฒ์ด UI์ ๋ ธํฐ ๋๋ ๊ฒ๊ณผ ๋ณ๊ฐ๋ก ๋งค๋ฒ ๋ค๋ฅธ ์์ ์ ํ๊ณ ์ถ์ ๋ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
์๋ฅผ ๋ค์ด WhenAny๊ฐ ์๋ ๋ค๋ฅธ ํ๋ ์์ํฌ๋ค์ ์ด๋ฒคํธ ์ด๊ทธ๋ฆฌ๊ฒ์ดํฐ ๊ธฐ๋ฅ์ ํตํด ๋ฉ์ธ์ง๋ก ํน์ ์์ฑ์ ๊ตฌ๋ ํ๊ณ ๋ฉ์ธ์ง๋ฅผ ๋์ง๋ฉด
๊ทธ ๋ฉ์ธ์ง๋ฅผ ์ฒ๋ฆฌํ๋ ๊ณณ์์ ์ด๊ฒ์ ๊ฒ ์์ ์ ํด์ผํ๋ ๋ถ๋ถ์ธ๋ฐ, RxUI์์๋ WhenAny์์ ๋ฉ์ ์ ๋์ ์ ์์ ์ ๊ตฌํํ ์ ์๋ค.
๋ฌผ๋ก RxUI์๋ ๋ฉ์ ์ ๊ธฐ๋ฅ์ด ์กด์ฌํ๊ธดํ์ง๋ง WhenAny ๊ธฐ๋ฅ์ด ๋์ฑ ์ ํธ๋๋ ๊ธฐ๋ฅ์ด๋ค.
| An advanced, composable, reactive model-view-viewmodel framework
๋ํ ์๋์ ๋ฐฉ๋ฒ์ ์ด์ฉํ๋ฉด ๋ฉ์ธ์ง์ ํผํ ์ ์๋ค.
| An advanced, composable, reactive model-view-viewmodel framework
-
ํ์ฌ ์ค์๊ฐ์ผ๋ก CCU ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ ์์์ฑ์ด ๋ฐ๋ก ํ์์๋ค. ํ์ํ๋ค๋ฉด ํ๋ก๊ทธ๋จ ํ๋ฉด์ ์ค์์ ๋์ฐ๋ ์ต์ ์ ๋ ๋์ค์ ์๊ฐํด๋ณด๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
-
Avalonia MVVM Template์ผ๋ก ๋ง๋ค์ด์ ์ต์ ๋ฒ์ ์ธ Avalonia UI 11 Preview 2 ๋ฒ์ ์ผ๋ก ์งํํ ๋ Nuget์์ Avalonia.Themes.Fluent ์ ์ถ๊ฐ ๋ค์ด๋ก๋๊ฐ ํ์ํ๋ค. ๋ค์ด๋ก๋ ํ์ง ์์ผ๋ฉด ๋น๋์๋ฌ๊ฐ ๋ฐ์ํ๋ค. Template์ ์ ๋ค์ด ์์ด์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉฐ ์ถํ ๊ฐ์ ๋ ๊ฒ ๊ฐ๋ค.
-
ํ์ฌ ReactiveUI์ ViewLocator ๋ฐฉ์์ด ์๋ AvaloniaUI์ ViewLocator๋ก View๋ฅผ ๋ถ๋ฌ์ค๊ณ ์๋๋ฐ ๋์ค์ IRoutingViewModel์ ์ฌ์ฉํ ReactiveUI์ Routing ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
Advanced Avalonia+ReactiveUI+Routing example ยท Issue #3605 ยท AvaloniaUI/Avalonia ยท GitHub
๋ฌผ๋ก AvaloniaUI์ MVVM Template์ ๊ธฐ๋ณธ ViewLocator๋ก๋ ์ ๋์ํ๋ค. -
ReactiveUI์ ๊ณต์ ๋ฌธ์์๋ ๋์์๋ฏ, MessageBus๋ ์ตํ์ ์๋จ์ผ๋ก ์ฌ์ฉํ๋ผ๊ณ ํ๋ค.
์ค์ ๋ก ๋ด๊ฐ ํด๋ณด๋๊น ๊ฐ๋น์ง๊ฐ ์์ฒญ๋๊ฒ ๋์ค๋ฉด์ ํ๋ก๊ทธ๋จ์ด ๋๋ ค์ง๋ ๊ฒฝํฅ์ด ์๋ค.
์ปค๋ฎค๋ํฐ ํดํท์ผ๋ก ํ์ ๋๋ ๊ทธ๋ฌ์ง๋ ์์์๋๋ฐ, ReactiveUI๊ฐ ์๋ํํ๊ณ , ์ถ์ํ๋ฅผ ๋ง์ด ํด๋์ ๊ฒ ๋๋ฌธ์ ์ค๊ฐ ์์๊ฐ์ฒด๊ฐ ๋ง์ด ์์ฑ๋์ด Messaging์ด ๋ฐฉํด๋ฐ๋ ๋๋์ด๋ค.
๋ฐ๋ผ์ ๊ฐ๋ฐ ๋งค์ปค๋์ฆ์ RxUI ๋ฐฉ์์ ๋ง๊ฒ ๋ฐ๊ฟ์ผํ๋ค.
While this class is provided because it is sometimes necessary, the MessageBus should be used only as a last resort . The MessageBus is effectively a global variable , which means it is subject to memory and event leaks, and furthermore, the detached nature of MessageBus means that itโs a
goto
whose destination is invisible. It also encourages bad design as many people will directly proxy View events to the ViewModel layer, which makes them not particularly ViewModelly.
-
๋งํฌ๋ฅผ ๋ณด๋ฉด Program.cs์ ์๋ UseReactiveUI() ๋ฉ์๋๋ฅผ ์ฐ๋ฉด RxApp.MainThreadScheduler๋ฅผ AvaloniaScheduler.Instance๋ก ์ด๊ธฐํ ํ๋ค๊ณ ํ๋ค.
์ค์ ๋ก UseReactiveUI ๋ฉ์๋๋ฅผ ๋์ปดํ์ผํด๋ณด๋ฉด ๊ทธ๋ฐ ์ฝ๋๋ฅผ ๋ณผ ์ ์๋ค.
๊ทธ๋ฐ๋ฐ ์ดํด๊ฐ ์๊ฐ๋ ๊ฒ์ ์๋ ์ฝ๋๋ฅผ ๋๋ฒ๊น ํ๋ ์์ ์ RxApp.MainThreadScheduler๋ฅผ ํ์ธํ๋ฉด System.Reactive.Concurrency.DefaultScheduler ๊ฐ ๋ค์ด์๋คโฆ
๋ฐ๋ผ์ RxApp.MainThreadScheduler๋ฅผ ์ฐ๋ ๊ณณ์๋ ์์ง์ AvaloniaScheduler.Instance๋ก ํด์ผํ ๊ฒ ๊ฐ๋ค. -
Quartz๋ Splat Generic Host์ ํจ๊ป ์ฌ์ฉํ๋๋ฐ, ์ด์ ๋ฅผ ๋ชจ๋ฅด๊ฒ ์ง๋ง ์ ํ ๋์ํ์ง ์์๋ค.
๊ทธ๋ฆฌ๊ณ RxUI๋ ์๋๊ณ Avalonia ๋ฌธ์ ์ธ๋ฏ ํ๋ฐ, ๋ง์ง๋ง์ ๋ฉ๋ชจํ ๊ฒ์ด ์์ ํ ๋๋ฌ๋ ๋ฌธ์ ๋ ์๋๋๋ค.
์ฌ์ ํ ๋น๋๊ธฐ๋ก ๋ฐ์ดํฐ๋ฅผ ์์ฃผ ์
๋ฐ์ดํธ ํด์ฃผ๋ฉด ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ ์ ๋ชจ๋ํฐ๋ง ํ๋ก๊ทธ๋จ์๋ ๋ณด๋ ๋ฐฉ๋ฒ์ ๋ฉ๋ด๊ฐ ์ฌ๋ฌ๊ฐ ์์ด์ ๊ทธ ๋ฒํผ๋ค์ ๋ง ํด๋ฆญํ๋ฉด ๋ณด๋ ๋ฐฉ์์ด ๋ง๊ตฌ ๋ฐ๋๋๋ฐ,
UI์์ ์
๋ฐ์ดํธํ ๊ฒ์ด ๋ง์ผ๋ฉด ์ค๋ฅ๊ฐ ์ข
์ข
๋ฉ๋๋ค. (๊ฐ๋งํ ์ผ๋๋ฉด ๋ฌธ์ ์๋จ)
ํ์ง๋ง ์ด ๋ถ๋ถ์ ๊ฐ์ ๋ ๊ฒ์ด๋ผ๊ณ ๋ฏฟ๊ณ ์งํํด์ผํ ๊ฒ ๊ฐ์ต๋๋ค. (๋ฌผ๋ก Toolkit์ 11๋ฒ์ ์๋ ๋น๋ ์๋ฌ ์์ด ์ง์ํด์ค๋ค๋ฉด RxUI๋ฅผ ์์จ๋ ๋๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋๋ ๋ถ๋ถ์ ์๋ ๊ฒ ๊ฐ์ต๋๋ค.)