Maui
Maui copied to clipboard
StateContainer
Description of Change
Displaying a specific view when your app is in a specific state is a common pattern throughout any mobile app. Examples range from creating loading views to overlay on the screen, or on a subsection of the screen. Empty state views can be created for when there's no data to display, and error state views can be displayed when an error occurs.
Based on StateLayout from the Xamarin Community Toolkit, originally created by Steven Thewissen.
Linked Issues
- Fixes #120
PR Checklist
- [x] Has a linked Issue, and the Issue has been
approved(bug) orChampioned(feature/proposal) - [x] Has tests (if omitted, state reason in description)
- [x] Has samples (if omitted, state reason in description)
- [x] Rebased on top of
mainat time of PR - [x] Changes adhere to coding standard
- [ ] Documentation created or updated: https://github.com/MicrosoftDocs/CommunityToolkit/pull/XYZ
Additional information
There are some changes since the original implementation/proposal. Summary:
StateLayoutrenamed toStateContainer.- Removed
StateViewwrapper, Views can now be states directly by defining aStateKeyproperty. CurrentStateis now a string binding.- Removed
CurrentCustomStateKey. - Removed
RepeatCountandTemplate.
@pictos thanks for the review, I've made updates per your requests. I also added a StateLayout example page to the sample app.
Regarding the using CommunityToolkit.Maui.Core, that's because I moved the LayoutState enum into the Primitives folder in Core. I saw some other common enums in there too...is that the right place for it? So far, it's used by all the new StateLayout stuff, StateToBoolConverter, and the associated samples and tests for each.
I have some idea-proposal about design to simplify the StateLayout.
- Rename StateLayout to StateContainer
- Replace
IList<StateView>withIList<IView>so we can put any view inside it (no need to put a view inside another view) - Remove StateView
- Create StateKey attached property for IView, so each view in StateContainer can describe its state
Rename StateLayout to StateContainer
Agreed. FYI @nicjay we decided on this name change in today's standup.
Replace IList<StateView> with IList<IView>
What if we change IList<StateView> to IDictionary<LayoutState, IView>?
This would allow us to still leverage the functionality of the LayoutState enum and automatically change the displayed view whenever LayoutState changes.
One limitation this would raise is it would limit us to only use one LayoutState.CustomState. But I imagine most devs don't use multiple custom states.
We still have Custom LayoutState, where a user needs to specify his own key. It is the same as a user choosing a predefined state like Save or Loading. I would prefer to remove this enum and use a string instead.
Just FYI - We aren't able to remove the LayoutState enum from the project because it's used by StateToBoolConverter
Thanks guys, StateContainer it is.
I love the idea of less view nesting. There's a lot to unpack:
I imagine a potential removal of StateView (and use IView directly) looks something like this?
<VerticalStackLayout mct:StateContainer.CurrentState="{Binding CurrentState}">
<mct:StateContainer.StateViews>
<VerticalStackLayout mct:StateView.StateKey="Loading">
<Label Text="Label of the First State" />
</VerticalStackLayout>
<HorizontalStackLayout mct:StateView.StateKey="Error">
<Label Text="Label of the Second State" />
</VerticalStackLayout>
<Label mct:StateView.StateKey="Success" Text="I am a whole state on my own!" />
</mct:StateContainer.StateViews>
<Label Text="Default Content" />
</VerticalStackLayout>
- Where would the StateKey and other attached property live? Is there still a StateView (now static) class containing these, or lumped into StateContainer, or elsewhere?
- I can see the case for custom string states only, but if LayoutState can't be removed, might as well keep using it. (Then again, is anyone actually using StateToBooleanConverter without StateLayout?)
- For RepeatCount functionality. XCT version relied on DataTemplates inside a Template property. I assume it could work similarly but perhaps there's a better way.
The new design looks much cleaner now.
- It's always hard to decide what is the right place for it. We don't want to have a lot of folders but also can't put everything in one. Let's for now keep it in Layout. We can move it later.
- I would keep LayoutState in Converter. For user it doesn't matter if he uses enum or string. For us it's additional code that we have to support. So I propose assuming we always use LayoutState.Custom and StateKey is string.
- I don't know much about RepeatCount. Is it used for displaying the same state view multiple times? Is it required to be a DataTemplate?
Update
I made some changes based on above discussion and @VladislavAntonyuk's suggestions. Usage is as in https://github.com/CommunityToolkit/Maui/pull/673#issuecomment-1270808062 and code logic is simplified quite a bit.
StateLayoutrenamedStateContainerStateViewis now static, no longer inherits ContentView, and holds an attached StateKey string property forViewclasses.- This means View classes can now be states directly instead of wrapping with a StateView.
- StateView.cs is moved back into CommunityToolkit.Maui.Layouts for now.
StateContainernow exclusively uses custom strings to determine state.- This allows infinite flexibility for states.
- Null or empty string corresponds to the default 'none' state, aka the original content.
- Trying to use a nonexistent state returns a placeholder error Label.
- The
LayoutStateenum is currently not used at all and put back to its original location in StateToBooleanConverter.
- I didn't include the RepeatCount property in this iteration - see below.
- Using
IList<View>instead ofIList<IView>- the bindings didn't play nice with IView.
Let me know what you think!
Naming
With the existence of BindableLayout, I can see a case for the original name "StateLayout" as a sort of cousin to BindableLayout. Both are a set of attached properties for Layout derived classes, and not being actual Layouts themselves. I'm fine with StateContainer, but it's food for thought. Also, does StateView still make sense for the attached view properties? StateItem?
RepeatCount
I couldn't find a way around the requirement for a DataTemplate when using a RepeatCount property. The View itself cannot be repeated/cloned/duplicated as far as I tried. A child DataTemplate could, but then that just replicates BindableLayout.
In any case, even though XCT had it, should RepeatCount be dropped for MCT? It adds complexity and doesn't seem particularly related to the concept of state. Now with less StateView wrapper XAML/code, a user can still do something like this easily enough, or handle their own BindableLayout.
<VerticalStackLayout mct:StateView.StateKey="MyLoadingState">
<controls:SkeletonLoaderControl />
<controls:SkeletonLoaderControl />
<controls:SkeletonLoaderControl />
</VerticalStackLayout>
should RepeatCount be dropped for MCT? It adds complexity and doesn't seem particularly related to the concept of state
Thanks @nicjay! I agree - maybe we remove the DataTemplate and RepeatCount for its v1 release.
These aren't really necessary for StateContainer to work. If anything, they seem like a bolted-on feature.
We could always implement them later without breaking changes if the we get demand from the community.
@nicjay the next step is to create the docs and open a PR on our doc repo. Let me know if you have any question
@pictos Docs PR created!
Sorry I haven't had a chance to check the docs yet but will try this week
Thanks all, glad to see this make it in 👍
Thanks all, glad to see this make it in 👍
Thanks for taking my baby and giving it a new life 😀😘
If I just want to show/hide the view, the best practice is IsVisible? StateContainer must be 2 and more StateView.StateKey? Or is it considered to be designed to hide (collapse) the view when CurrentState = null? Also does StateContainer have the ability to lazy loading as a displayed view? I hope this can be reflected in the documentation.