[英]How to set multiple StateNotifierProvider (s) with dynamicaly loaded async data?
I'm completely stuck with the task below.我完全坚持下面的任务。 So, the idea is to solve these steps using Riverpod
所以,我们的想法是使用Riverpod
解决这些步骤
Fetch data from db with some kind of Future async
while pausing the app (display SomeLoadingPage()
etc.)在暂停应用程序时使用某种Future async
从数据库中获取数据(显示SomeLoadingPage()
等)
Once the data has loaded:数据加载后:
2.1 initialize multiple global StateNotifierProvider
s which utilize the data in their constructors and can further be used throughout the app with methods to update their states. 2.1 初始化多个全局StateNotifierProvider
s,它们利用其构造函数中的数据,并且可以通过更新其状态的方法在整个应用程序中进一步使用。
2.2 then show MainScreen()
and the rest of UI 2.2 然后显示MainScreen()
和UI的rest
So far I've tried something like this:到目前为止,我已经尝试过这样的事情:
class UserData extends StateNotifier<AsyncValue<Map>> { // just <Map> for now, for simplicity
UserData() : super(const AsyncValue.loading()) {
init();
}
Future<void> init() async {
state = const AsyncValue.loading();
try {
final HttpsCallableResult response =
await FirebaseFunctions.instance.httpsCallable('getUserData').call();
state = AsyncValue.data(response.data as Map<String, dynamic>);
} catch (e) {
state = AsyncValue.error(e);
}}}
final userDataProvider = StateNotifierProvider<UserData, AsyncValue<Map>>((ref) => UserData());
final loadingAppDataProvider = FutureProvider<bool>((ref) async {
final userData = await ref.watch(userDataProvider.future);
return userData.isNotEmpty;
});
class LoadingPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return FutureBuilder(
future: ref.watch(loadingAppDataProvider.future),
builder: (ctx, AsyncSnapshot snap) {
// everything here is simplified for the sake of a question
final Widget toReturn;
if (snap.connectionState == ConnectionState.waiting) {
toReturn = const SomeLoadingPage();
} else {
snap.error != null
? toReturn = Text(snap.error.toString())
: toReturn = const SafeArea(child: MainPage());
}
return toReturn;},);}}
I intentionally use FutureBuilder
and not .when()
because in future i may intend to use Future.wait([])
with multiple futures我有意使用FutureBuilder
而不是.when()
因为将来我可能打算将Future.wait([])
与多个期货一起使用
This works so far, but the troubles come when I want to implement some kind of update() methods inside UserData
and listen to its variables through the entire app.到目前为止这是有效的,但是当我想在UserData
中实现某种 update() 方法并通过整个应用程序监听它的变量时,麻烦就来了。 Something like就像是
late Map userData = state.value ?? {};
late Map<String, dynamic> settings = userData['settings'] as Map<String, dynamic>;
void changeLang(String lang) {
print('change');
for (final key in settings.keys) {
if (key == 'lang') settings[key] = lang;
state = state.whenData((data) => {...data});
}
}
SomeLoadingPage()
appears on each changeLang()
method call. SomeLoadingPage()
出现在每个changeLang()
方法调用中。
In short: I really want to have several StateNotifierProvider
s with the ability to modify their state from the inside and listen to it from outside.简而言之:我真的希望有几个StateNotifierProvider
能够从内部修改它们的 state 并从外部收听它。 But fetch the initial state from database and make the intire app wait for this data to be fetched and these providers to be initilized.但是从数据库中获取初始 state 并让整个应用程序等待获取此数据并初始化这些提供程序。
So, I guess I figured how to solve this:所以,我想我想出了如何解决这个问题:
final futureExampleProvider = FutureProvider<Map>((ref) async {
final HttpsCallableResult response =
await FirebaseFunctions.instance.httpsCallable('getUserData').call();
return response.data as Map;
});
final exampleProvider = StateNotifierProvider<Example, Map>((ref) {
// we get AsyncValue from FutureNotifier
final data = ref.read(futureExampleProvider);
// and wait for it to load
return data.when(
// in fact we never get loading state because of FutureBuilder in UI
loading: () => Example({'loading': 'yes'}),
error: (e, st) => Example({'error': 'yes'}),
data: (data) => Example(data),
);
});
class LoadingPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return FutureBuilder(
// future: ref.watch(userDataProvider.future),
future: ref.watch(futureExampleProvider.future),
builder: (ctx, AsyncSnapshot snap) {
final Widget toReturn;
if (snap.data != null) {
snap.error != null
? toReturn = Text(snap.error.toString())
: toReturn = const SafeArea(child: MainPage());
} else {
// this is the only 'Loading' UI the user see before everything get loaded
toReturn = const Text('loading');
}
return toReturn;
},
);
}
}
class Example extends StateNotifier<Map> {
Example(this.initData) : super({}) {
// here comes initial data loaded from FutureProvider
state = initData;
}
// it can be used further to refer to the initial data, kinda like cache
Map initData;
// this way we can extract any parts of initData
late Map aaa = state['bbb'] as Map
// this method can be called from UI
void ccc() {
// modify and update data
aaa = {'someKey':'someValue'};
// trigger update
state = {...state};
}
}
This works for me, at least on this level of complexity.这对我有用,至少在这个复杂度上是这样。 I'll leave question unsolved in case there are some better suggestions.如果有更好的建议,我会留下未解决的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.