Flutter Riverpod design pattern (inhibit garbage collection) - flutter

I've written a Swift/IOS package to externalize and standardize all of my Social/Federated/Firebase authentication boilerplate (both SDK's and UI). I've taken it upon myself to port this to Flutter as a learning exercise ... but to also allow custom UI to be passed-in via config.
Since I'm new to Flutter & Riverpod, I'm afraid I'm making some serious mistake & want to get feedback from you experts before I go too deep.
The package is called "Social Login Helper" or SLH, and this is the public API I desire:
runApp(
slh.authStateBuilder(
builder: (authStatus) {
switch (authStatus.stage) {
case SlhResultStage.initializing:
return SplashScreen();
case SlhResultStage.unauthenticated:
// using Riverpod and Nav 2.0
return slh.authFlowUi;
case SlhResultStage.authenticated:
return ExampleApp(appKey, authStatus, slh.logoutCallback);
case SlhResultStage.wantsAnnonOnlyFeatures:
return ExampleApp(appKey, null, slh.startAuthCallback);
case SlhResultStage.excessiveFailures: // restart the app
return TotalFailure();
}
},
),
);
As you can see from the above, the State/Stream builder at root must never be garbage collected or purged. I'm unclear if and when Riverpod will dispose my provider, or if Dart itself will collect objects that must remain immortal. I'm also unsure whether to use a StreamProvider or a State provider??
As you can see below, I've created an intentional memory-leak (deadlock) to guard me. I'm sure it's an anti-pattern, but being novice, I'm not sure how else to guarantee immortality.
All guidance and explicit feedback would be most welcome.
class LivingAuthState extends StateNotifier<SlhResultStage> {
// create deadly embrace to prevent this from ever being collected
_Unpurgeable _up;
LivingAuthState() : super(SlhResultStage.initializing) {
//
final StreamProvider<SlhResultStage> rssp =
StreamProvider<SlhResultStage>((ref) {
return this.stream.asBroadcastStream();
});
_up = _Unpurgeable(this, rssp);
// how do I keep rssp from ever being collected??
}
StreamProvider<SlhResultStage> get authStatusStream => _up.rssp;
void logout() {
this.state = SlhResultStage.unauthenticated;
}
void restartLogin() {
this.state = SlhResultStage.unauthenticated;
}
}
class _Unpurgeable {
final LivingAuthState _aliveState;
final StreamProvider<SlhResultStage> rssp;
_Unpurgeable(this._aliveState, this.rssp);
}
One improvement I'd like to see in the Riverpod documentation is clarity on HOW LONG a provider will live, WITHOUT an active listener, before it will self-dispose / garbage-collect.
Ah, it looks like I can subclass AlwaysAliveProviderBase() to achieve the same goal ... I'll experiment with this.

Move your provider final to the top level. Riverpod providers are top-level final variables.
Also remember to wrap your app in the riverpod provider.

Related

How to get data from an API only once (on app creation, outside component or view) in Vue3 SPA, with Pinia store

Is it possible and is it a good practice to avoid fetching data from an API every time the router view is loaded or the component is Mounted?
The thing is that some data rarely changes (like a dropdown options list, imagine allowed animal picks for my app) and it's logical not to send a request every time to a server, instead on app creation would be sufficient.
Tried in App.vue, is that a common thing?
IN APP.vue
import { computed, onMounted, onUpdated, ref } from 'vue';
onMounted(()=>{
axios.get('/data')....
.then((res)=>{
store.property = res.data
...
})
})
I think having it on mount in the App.vue component is acceptable since the App component would not be remounted.
The ideal setup, however, depends on some other parameters like size of application and size of team that's maintaining it. In a large applications you might want to organize things in amore structured and consistent way so you and other folks working on the code know where to find things.
You could consider moving the API call into the pinia action.
store.loadMyData()
// instead of
axios.get('/data')
.then((res)=>{
store.property = res.data;
})
That way you have fewer lines of code in the component. Having "lean" components and moving "business logic" out of components usually makes for better organization which makes it easier to maintain.
Within the action, you can track the state of the API
const STATES = {
INIT: 0,
DONE: 1,
WIP: 2,
ERROR: 3
}
export const useMyApiStore = defineStore('myapi', {
state: () => ({
faves: [],
favesState: STATES.INIT
}),
actions: {
loadMyData() {
this.store.favesState = STATES.WIP;
axios.get('/data')
.then((res) => {
this.store.property = res.data;
this.store.favesState = STATES.DONE;
})
.catch((e) => {
this.store.favesState = STATES.ERROR;
})
},
},
getters: {
isLoaded(){
return this.store.favesState === STATES.DONE;
}
isLoading(){
return this.store.favesState === STATES.WIP;
}
}
})
This is, obviously, more verbose, but allows for the components to be smaller and contain less logic. Then, for example, in your component you can use the getter isLoading to display a loading indicator, or use isLoaded inside another component to determine whether to show it.
Yes, this is a oft used way to load some data into the Vue App.
You could also load data before the Mounting in beforeMount() or created() Lifecycle Hooks (see Vue Lifecycle Diagram) to prevent unnecessary HTML updates.

Update global state after RTK Query loads data

I've noticed a problem with splitting responsibilities in React components based on the fetched data using RTK Query.
Basically, I have two components like HomePage and NavigationComponent.
On HomePage I'd like to fetch the information about the user so that I can modify NavigationComponent accordingly.
What I do inside HomePage:
import { setNavigationMode } from "features/nav/navSlice";
export default function HomePage() {
const {data: user} = useGetUserDataQuery();
const dispatch = useAppDispatch();
const navMode = user ? "all-options" : "none";
dispatch(setNavigationMode(navMode)); // here I change the default Navigation mode
return <MainLayout>
<Navigation/>
<Content/>
<Footer/>
</MainLayout>;
}
The HomePage is a special Page when the NavigationComponent shouldn't display any options for the not logged in user.
Other pages presents additional Logo and Title on Nav.
React communicates:
Warning: Cannot update a component (NavComponent) while rendering a different component (HomePage). To locate the bad setState() call inside HomePage, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
Not sure what is the right way to follow.
Whether the state should be changed in GetUser query after it is loaded - that doesn't seem to be legit.
problem is dispatch calls every render. Instead you can create a navigationSlice (if you don't have already) and use extraReducers for matching your authorization action like:
extraReducers: (builder) => {
builder.addMatcher(
usersApi.endpoints.login.matchFulfilled,
(state, { payload }) => {
if (payload.user) {
state.navigationMode = "all-options"
}
}
);
}
This way, state.navigationMode will only change when authorization changes
The solution was too obvious. The dispatch should be run in useEffect.
import { setNavigationMode } from "features/nav/navSlice";
export default function HomePage() {
const {data: user} = useGetUserDataQuery();
const dispatch = useAppDispatch();
const navMode = user ? "all-options" : "none";
// changed lines
useEffect( () => {
dispatch(setNavMode(navMode));
}, [navMode, dispatch]);
// /changed lines
return <MainLayout>
<Navigation/>
<Content/>
<Footer/>
</MainLayout>;
}
Thank you #papa-xvii for the hint with changing the navMode after user login. That solves the second problem I had.
However I cannot accept the answer as it does not solve the problem I described above.

Is a constant variable more performant when switching between widgets and if so how do you achieve this at the entry point of app?

I try to use the same code for both web and android. Where the code differs I switch between widgets based on a global variable.
Is the performance worse when using a non constant / non final variable when switching between widgets? I'm thinking, because the variable is not final or constant and can be changed at any point, Flutter will not be able to optimise the code. Is that true? If inefficient, how do I make my code efficient?
eg.
I have two main files and set my AppType enum in each
[appType.dart]
AppType appType; //can't think of how to make this constant or final
[android_main.dart]
void main() {
appType = AppType.and;
[web_main.dart]
void main() {
appType = AppType.and;
In my widgets I switch where I need a widget specific for the web or android
if(appType == AppType.web)
return MyWidgetWeb();
else
return MyWeigetAnd();
Yes, a constant is more efficient, mainly because of tree-shaking.
Assume that you have the following types:
enum AppType {
mobile,
web,
}
class Mobile {}
class Web {}
Then when you write:
const type = AppType.web;
void main() {
if (type == AppType.web) {
print(Web());
}
else if (type == AppType.mobile) {
print(Mobile());
}
}
Then when compiling the code, the compiler knows that the if block will always be reached, and the else if never will.
As such:
the conditions are removed. When compiled, the code will be:
const type = AppType.web;
void main() {
// no `if` performed
print(Web());
}
Mobile will not be bundled in the executable, so you have a lighter application.
To fully benefit from this behavior, you can use Dart "defines", using int/bool/String.fromEnvironment, which allows you to define constants that behave differently depending on some external build parameters.
The way such constant would look like is:
const isWeb = bool.fromEnvironment('isWeb', defaultValue: false);
Which you can then control using arguments on flutter run and flutter build commands:
flutter build <whatever> --dart-define=isWeb=true

LitElement with data from Firestore

I've been trying to dynamically insert data from Firestore into my component.
Currently, I'm using the firstUpdated() lifecycle. My code works but it fell like there's a better way of doing this.
This is my current component.
static get properties() {
return {
firebaseData: {type:Object},
}
}
constructor() {
super()
this.firebaseData = {}
}
firstUpdated() {
firestore.doc(`...`).get()
.then(doc => {this.firebaseData = doc.data()})
})
.catch(err => console.error(err))
}
render() {
return html `${firebaseData.title}`
}
I was hope someone with more experience would be open to sharing their knowledge. Thanks in advance!
firstUpdated should be used when you need to interact with shadow DOM elements inside your web component, as they aren't created until then. It's the earliest moment when you can be sure your component DOM exists.
I would prefer to do the firebase call earlier, even in the constructor.
The idea is, your firebase call isn't dependent of the rendering, so you could directly do it at the earliest moment, and as in the callback of the function you update the firebaseData property, a new rendering cycle will be done then.

how to handle multiple `loading` states with redux architecture in Flutter?

How do you guys handle multiple loading states with redux pattern in Flutter?
So here is my case:
I have 3 pages, each page calls a different API and shows a loading HUD while requesting the API.
Sub-Question#1 How do i manage these isLoading states?
If i do it in the AppState , i need to add multiple boolean properties to it, something like:
class AppState {
final bool apiOneIsLoading;
final bool apiTwoIsLoading;
final bool apiThreeIsLoading;
// other properties, bla bla...
}
However, Adding to many properties to the AppState class doesn’t sounds great I guess...
Sub-Question#2 How do I update the UI when the loading state changes?
One solution I come up with is to create actions for both loading and loaded state, like so:
class SomeMiddleware extends MiddlewareClass<AppState> {
#override
void call(Store<AppState> store, dynamic action, NextDispatcher next) {
if (action is CallAPIOneAction) {
store.dispatch(APIOneLoadingAction());
// call api one
api.request().then((result){
store.dispatch(APIOneLoadedAction())
})
}
}
}
But if I do this, I need to create 2 extra actions for each API call, is this a good idea? is it okay to change or send new actions in middleware class on the fly?
Please let me know if you have a good solution, thanks!