Global State
Closed this issue · 9 comments
Hey ado,
just occured to me so I wanted to ask - does it make sense to build something like a GlobalState
?
It can be based on IParameter
. Components would somehow subscribe to it and invalidate themselves if there is a change.
Or am I unintentionally describing ReactorData or some other common concept?
Absolutely! It makes sense, I usually have a GlobalState class that contains app-wide state values that I then inject as a parameter in my components (and yes components are invalidated automatically when it changes).
ReactorData is another package designed over SwiftData that may be useful to handle more advanced scenarios like:
- cache data received from remote services
- add off-line support to an app
- use ef core to store data locally and sync back to the server
Absolutely! It makes sense, I usually have a GlobalState class that contains app-wide state values that I then inject as a parameter in my components (and yes components are invalidated automatically when it changes).
Would you mind showing me a sample?
Since GetOrCreateParameter()
is under Component, I probably can't use IParameter
in a static class for GlobalState
public static partial class GlobalState
{
[Param]
static IParameter<MyType> MyParam;
}
Also this would generate a non-static constructor, which should piss the compiler off.
A workaround would probably be storing the data as GlobalState properties and maybe trying to set Parameters in sets? Sounds pretty clunky, but even if that was the case, how do I get a Parameter reference outside of a Component anyway?
Uff, a bit confused I suppose.
Eventually what I'm trying to do is to centralize my data since I need to pull from my Api when my app starts. All the service methods are async, and don't really play well with OnMounted()
- it leads to situations where my app launches, UI thread isn't blocked.
Sorry, I'm more of a backend person so - maybe I should just embrace async void
s and just handle data being null for a bit. Put "loading..."s to everywhere. If I get to solve centeralization with Parameters, at least I get to invalidate relevant parts without pub-sub'ing to WeakReferenceMessengers everywhere.
Using a parameter is pretty easy, just create a class containing your state and inject it into your components using the [Param] attribute.
Have you looked at the documentation:
https://adospace.gitbook.io/mauireactor/components/component-parameters
KeeMind sample (Global state class passed as parameter):
https://github.com/adospace/kee-mind/blob/main/src/KeeMind/Pages/MainPage.cs
Trackizer app (User class passed over as parameter):
https://github.com/adospace/mauireactor-samples/blob/main/TrackizerApp/Pages/HomeScreen.cs
Yep, I mean to have parameters in a static centeralized GlobalState as well so I can set them. Getting them in a component is fairly straightforward.
Like:
// GlobalState.cs
public static partial class GlobalState
{
[Param]
static IParameter<MyData> MyParam; // Can't do this since this is not a Component
public static async Task RefreshData()
{
MyParam.Set(_ => _.Value = someObject); // Obviously can't do this too
}
}
// SomeComponent.cs
partial class SomeComponent : Component<SomeState>
{
// Get and do stuff with MyParam
[Param]
IParameter<MyData> MyParam;
}
Isn't this what you meant initially as well?
ok I'm, not sure what you're trying to achieve but in MauiReactor you should create a class holding the state (global or component state) that should not contain methods.
From what I understand you're mixing different behaviors that should be split up into different classes:
Something like:
interface IApiService
{
Task<DataModel> LoadDataFromServer();
}
class MyGlobalState
{
public DataModel? MyData {get;set;}
}
class MyComponent
{
[Param]
IParameter<MyGlobalState> _globalState;
[Inject]
IApiService _myService;
override OnMounted()
{
Task.Run(LoadDataFromServer);
}
public override Render()
{
return ContentPage(...render _globalState.Value );
}
async Task LoadDataFromServer()
{
var dataLoaded = await _myService.LoadDataFromServer();
_globalState.Set(_ => _.MyData = dataLoaded);
}
}
Yup.
I was imagining a static class with all application-wide data as parameters, and components would reference them too, and work like an implicit pub-sub model:
static class GlobalState
{
[Parameter]
IParameter<User> User;
[Parameter]
IParameter<List<Report>> Reports;
[Parameter]
IParameter<SomeData> Data;
}
Then we could set them (not necessarily on GlobalState, maybe in services):
class UserService
{
public async Task GetUser()
{
// ...
GlobalState.User.Set(_ => _.Value = user);
}
}
And components referencing this parameter would be updated:
class UserComponent : Component
{
[Parameter]
IParameter<User> User;
public override VisualNode Render()
{
// Do something with GlobalState.User
}
}
Hope that made some sense.
But I liked your sample as well, not centeralized as I put it, but it manages to communicate component to component, probably should result similarly.
I see, probably you could then be interested in state managers ala Flux, like Fluxor
https://github.com/mrpmorris/Fluxor
This is a POC integration for MauiReactor
https://gist.github.com/adospace/cbede42c410c642dbdbcafe9ece5e90b
you could easily create a Component derived class that invalidates the component when the global state changes and derive your components from it.
Thanks a lot man!
Probably off-topic for this repository but, based on this in ReactorData's readme:
Without storage, ReactorData is more or less a state manager, keeping all the entities in memory.
Do you have plans for ReactorData to work like this?
Using ReactorData just as a global state manager is not advisable because ReactorData container is designed to maintain lists of models (IQuery) and notify subscribers when a new record is added/edited or removed from these lists.
I'm working on a new sample application that shows how to use MauiReactor+ReactorData to fetch and cache data locally.
I don't know the general context of your app but maybe that sample code could help in your case too.